{
  "openapi": "3.0.3",
  "info": {
    "title": "tl;dr News API",
    "description": "\nThe tl;dr News API provides programmatic access to AI-summarized news articles.\n\n## Authentication\n\nAll API endpoints require authentication via API key. Include your API key in requests using one of these methods:\n\n- **Header**: `X-API-Key: your-api-key`\n- **Bearer Token**: `Authorization: Bearer your-api-key`\n\n## Rate Limiting\n\nAPI keys have daily rate limits configured by administrators. Rate limit information is included in response headers:\n\n- `X-RateLimit-Limit`: Your daily request limit\n\nWhen you exceed your rate limit, you'll receive a 429 error. Limits reset at midnight UTC.\n\n## Getting an API Key\n\n1. Create an account at https://tldrdailynews.com/login\n2. Visit the Developer Portal at https://tldrdailynews.com/developer\n3. Request an API key with a description of your intended use\n4. Wait for administrator approval\n5. Your API key will be shown once - save it securely!\n\n## Permissions\n\nAPI keys can have different permission levels:\n\n- `read:articles` - Read article content and search\n- `read:stats` - Read statistics, tags, and categories\n- `write:jobs` - Queue articles for processing (restricted)\n- `admin` - Full access (restricted)\n    ",
    "version": "1.0.0",
    "contact": {
      "name": "tl;dr News Support",
      "url": "https://tldrdailynews.com/"
    }
  },
  "servers": [
    {
      "url": "https://tldrdailynews.com/api/v1",
      "description": "Production API"
    }
  ],
  "security": [
    {
      "apiKey": []
    },
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for authentication"
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key as Bearer token"
      }
    },
    "schemas": {
      "Article": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique article identifier"
          },
          "slug": {
            "type": "string",
            "description": "URL-friendly slug"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Publication timestamp"
          },
          "category": {
            "type": "string",
            "description": "Main category (e.g., technology, business)"
          },
          "sourceUrl": {
            "type": "string",
            "format": "uri",
            "description": "Original article URL"
          },
          "source": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "Source publication name"
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Source website URL"
              }
            }
          },
          "title": {
            "type": "string",
            "description": "AI-generated headline"
          },
          "summary": {
            "type": "string",
            "description": "AI-generated summary (tl;dr)"
          },
          "subcategory": {
            "type": "string",
            "description": "AI-assigned subcategory"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "AI-generated tags"
          },
          "metrics": {
            "type": "object",
            "nullable": true,
            "properties": {
              "savedTimeMinutes": {
                "type": "number",
                "description": "Reading time saved"
              },
              "savedTimePercent": {
                "type": "number",
                "description": "Percentage of time saved"
              },
              "originalWords": {
                "type": "integer",
                "description": "Original article word count"
              },
              "originalMinutes": {
                "type": "number",
                "description": "Original reading time"
              },
              "tldrWords": {
                "type": "integer",
                "description": "Summary word count"
              }
            }
          },
          "image": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Article image URL"
          },
          "imageDimensions": {
            "type": "object",
            "nullable": true,
            "properties": {
              "width": {
                "type": "integer"
              },
              "height": {
                "type": "integer"
              }
            }
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Tag name"
          },
          "articleCount": {
            "type": "integer",
            "description": "Number of articles with this tag"
          }
        }
      },
      "Category": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string",
            "description": "Category URL slug"
          },
          "name": {
            "type": "string",
            "description": "Display name"
          }
        }
      },
      "Stats": {
        "type": "object",
        "properties": {
          "articles": {
            "type": "integer",
            "description": "Total article count"
          },
          "categories": {
            "type": "integer",
            "description": "Number of categories"
          },
          "sources": {
            "type": "integer",
            "description": "Number of unique sources"
          },
          "timeSavedMinutes": {
            "type": "integer",
            "description": "Total reading time saved (minutes)"
          },
          "timeOriginalMinutes": {
            "type": "integer",
            "description": "Total original reading time (minutes)"
          }
        }
      },
      "Job": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Job identifier"
          },
          "type": {
            "type": "string",
            "description": "Job type"
          },
          "articleId": {
            "type": "string",
            "description": "Associated article ID"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "complete",
              "error",
              "skipped",
              "retry"
            ],
            "description": "Current job status"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "finishedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "message": {
            "type": "string",
            "nullable": true,
            "description": "Status message or error"
          },
          "retryCount": {
            "type": "integer",
            "description": "Number of retry attempts"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "description": "Current page number"
          },
          "limit": {
            "type": "integer",
            "description": "Items per page"
          },
          "total": {
            "type": "integer",
            "description": "Total item count"
          },
          "totalPages": {
            "type": "integer",
            "description": "Total number of pages"
          },
          "hasMore": {
            "type": "boolean",
            "description": "Whether more pages exist"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string"
              },
              "code": {
                "type": "string"
              },
              "status": {
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "message": "Authentication required",
                "code": "UNAUTHORIZED",
                "status": 401
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Permission denied",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "message": "Permission 'read:articles' required",
                "code": "PERMISSION_DENIED",
                "status": 403
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "message": "Rate limit exceeded. Daily limit: 1000 requests",
                "code": "RATE_LIMIT_EXCEEDED",
                "status": 429
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "success": false,
              "error": {
                "message": "Article not found",
                "code": "NOT_FOUND",
                "status": 404
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/register": {
      "post": {
        "summary": "Register a bot/agent",
        "description": "Self-register as an AI agent to get an API key. No authentication required. Keys are auto-approved with read-only permissions.",
        "tags": [
          "Registration"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "description"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 50,
                    "description": "Your bot/agent name",
                    "example": "NewsBot"
                  },
                  "description": {
                    "type": "string",
                    "minLength": 10,
                    "maxLength": 500,
                    "description": "What your bot does",
                    "example": "Daily AI news summarizer for my Discord server"
                  },
                  "owner": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "Owner identifier (Twitter handle, email, etc.)",
                    "example": "@myhandle"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registration successful - API key returned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "api_key": {
                          "type": "string",
                          "description": "⚠️ SAVE THIS! Shown only once.",
                          "example": "tldr_abc123..."
                        },
                        "key_id": {
                          "type": "string",
                          "description": "Key identifier"
                        },
                        "name": {
                          "type": "string"
                        },
                        "permissions": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "example": [
                            "read:articles",
                            "read:stats"
                          ]
                        },
                        "dailyRateLimit": {
                          "type": "integer",
                          "example": 500
                        },
                        "important": {
                          "type": "string"
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "header": {
                              "type": "string"
                            },
                            "bearer": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/articles": {
      "get": {
        "summary": "List articles",
        "description": "Get a paginated list of articles with optional filtering",
        "tags": [
          "Articles"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            },
            "description": "Items per page (max 100)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "Page number"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category slug"
          },
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Filter by tags (comma-separated)"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Articles published after this date"
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Articles published before this date"
          },
          {
            "name": "excludeIds",
            "in": "query",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Article IDs to exclude"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Article"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/articles/slug/{slug}": {
      "get": {
        "summary": "Get article by slug",
        "description": "Retrieve a single article by its URL-friendly slug",
        "tags": [
          "Articles"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Article slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Article"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/articles/id/{id}": {
      "get": {
        "summary": "Get article by ID",
        "description": "Retrieve a single article by its unique identifier",
        "tags": [
          "Articles"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Article ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Article"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/tags": {
      "get": {
        "summary": "List tags",
        "description": "Get all tags with article counts",
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "Items per page (max 500)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "Page number"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter tags by name"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "popular",
                "alphabetical"
              ],
              "default": "popular"
            },
            "description": "Sort order"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Tag"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/search": {
      "get": {
        "summary": "Search articles",
        "description": "Full-text search across article titles, summaries, and tags. Results are ranked by relevance. Great for AI agents to find articles on specific topics.",
        "tags": [
          "Search"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 500
            },
            "description": "Search query (min 2 characters). Supports natural language queries.",
            "example": "artificial intelligence"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            },
            "description": "Items per page (max 100)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "Page number"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category slug (e.g., technology, business)"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response with relevance-ranked articles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/Article"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "relevanceScore": {
                                "type": "number",
                                "description": "Text search relevance score"
                              },
                              "excerpt": {
                                "type": "string",
                                "nullable": true,
                                "description": "Short excerpt from the article"
                              }
                            }
                          }
                        ]
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/categories": {
      "get": {
        "summary": "List categories",
        "description": "Get all news categories",
        "tags": [
          "Categories"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Category"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/stats": {
      "get": {
        "summary": "Get statistics",
        "description": "Get global or filtered statistics about articles",
        "tags": [
          "Statistics"
        ],
        "parameters": [
          {
            "name": "tag",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by tag"
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by category"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Stats"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/jobs/queue": {
      "post": {
        "summary": "Queue article for processing",
        "description": "Queue an existing article for reprocessing. Requires write:jobs permission.",
        "tags": [
          "Jobs"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "articleId": {
                    "type": "string",
                    "description": "Article ID to reprocess"
                  }
                }
              },
              "example": {
                "articleId": "abc123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Job queued successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "queued": {
                          "type": "boolean"
                        },
                        "message": {
                          "type": "string"
                        },
                        "jobId": {
                          "type": "string"
                        },
                        "articleId": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/jobs/id/{id}": {
      "get": {
        "summary": "Get job status",
        "description": "Get the status of a processing job",
        "tags": [
          "Jobs"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Job ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/Job"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/jobs": {
      "get": {
        "summary": "List jobs",
        "description": "Get a paginated list of jobs with optional filtering",
        "tags": [
          "Jobs"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            },
            "description": "Items per page (max 100)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "Page number"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "processing",
                "complete",
                "error",
                "skipped",
                "retry"
              ]
            },
            "description": "Filter by status"
          },
          {
            "name": "articleId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by article ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "success": {
                      "type": "boolean",
                      "example": true
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Job"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Registration",
      "description": "Bot/agent self-registration (no auth required)"
    },
    {
      "name": "Articles",
      "description": "News article endpoints"
    },
    {
      "name": "Search",
      "description": "Full-text search for AI agents and apps"
    },
    {
      "name": "Tags",
      "description": "Article tag endpoints"
    },
    {
      "name": "Categories",
      "description": "News category endpoints"
    },
    {
      "name": "Statistics",
      "description": "Site statistics endpoints"
    },
    {
      "name": "Jobs",
      "description": "Article processing job endpoints"
    }
  ]
}