{
  "openapi": "3.1.0",
  "info": {
    "title": "Aurith News API",
    "version": "1.0.0",
    "description": "Public /v1 key routes and the unauthenticated sample route. Account and payment routes are intentionally excluded."
  },
  "servers": [
    {
      "url": "https://api.aurith.ca"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/v1/items": {
      "get": {
        "summary": "List newest items",
        "parameters": [
          {
            "name": "lang",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated language codes. Defaults to saved languages or all languages permitted by the key."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated exact source keys. Defaults to saved settings or all entitled sources."
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated categories, limited to the key's packs."
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated regions, limited to the key's packs."
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional timestamp filter on held_at. The implementation accepts Python ISO timestamps and assumes UTC when no timezone is supplied; RFC 3339 is recommended."
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "id,lang,headline,body,source,source_name,category,region,published_at,held_at,written_at,latency_seconds,subjects"
            },
            "description": "Comma-separated whitelist. Omitted or empty returns all item fields."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Use the preceding response's next value to continue toward older items."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 50,
              "minimum": 1
            },
            "description": "Default is 50 and may be configured; minimum is 1 and maximum is configured (200 by default). Out-of-range values return 400."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ItemsPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/key": {
      "get": {
        "summary": "Read key entitlements",
        "responses": {
          "200": {
            "description": "Key metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/languages": {
      "get": {
        "summary": "List languages",
        "responses": {
          "200": {
            "description": "Available languages and key eligibility.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "languages"
                  ],
                  "properties": {
                    "languages": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "code",
                          "name",
                          "yours"
                        ],
                        "properties": {
                          "code": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "yours": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sources": {
      "get": {
        "summary": "List sources",
        "responses": {
          "200": {
            "description": "Sources visible to the key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "sources",
                    "count"
                  ],
                  "properties": {
                    "sources": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "source",
                          "source_name",
                          "yours",
                          "note"
                        ],
                        "properties": {
                          "source": {
                            "type": "string"
                          },
                          "source_name": {
                            "type": "string"
                          },
                          "yours": {
                            "type": "boolean"
                          },
                          "note": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/settings": {
      "get": {
        "summary": "Read saved key settings",
        "responses": {
          "200": {
            "description": "Saved filter defaults.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettingsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "summary": "Save key filter defaults",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SettingsInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SettingsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/portal": {
      "get": {
        "summary": "Create a billing portal link",
        "responses": {
          "200": {
            "description": "Stripe customer portal link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "url",
                    "plan",
                    "status"
                  ],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "plan": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "Key was not purchased through Stripe or has no subscription.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "Stripe refused to create the portal link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "summary": "Read feed status",
        "responses": {
          "200": {
            "description": "Feed item count and recency.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "newest_held_at",
                    "seconds_since_newest",
                    "languages",
                    "asked_at"
                  ],
                  "properties": {
                    "items": {
                      "type": "integer"
                    },
                    "newest_held_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "date-time"
                    },
                    "seconds_since_newest": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "languages": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "asked_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/sample": {
      "get": {
        "summary": "Get a delayed public sample",
        "security": [],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "maxLength": 40
            },
            "description": "A published category: business, politics, crime, tech, health or weather. Empty/unknown category returns 400."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "minimum": 1,
              "maximum": 10
            },
            "description": "Number of returned items; category cache may contain fewer."
          }
        ],
        "responses": {
          "200": {
            "description": "Up to ten English items at least three hours old.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SampleResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/images/{key}.png": {
      "get": {
        "summary": "A story's image (the url in item.image). Public, cacheable.",
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The image",
            "content": {
              "image/png": {},
              "image/jpeg": {}
            }
          },
          "404": {
            "description": "Unknown image"
          },
          "429": {
            "description": "Too many requests; see Retry-After"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from the Aurith account page."
      }
    },
    "schemas": {
      "Item": {
        "type": "object",
        "required": [
          "id",
          "lang",
          "headline",
          "body",
          "source",
          "source_name",
          "category",
          "region",
          "published_at",
          "held_at",
          "written_at",
          "latency_seconds",
          "subjects",
          "image"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          },
          "headline": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "source_name": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "held_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "written_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "latency_seconds": {
            "type": [
              "integer",
              "null"
            ]
          },
          "subjects": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "image": {
            "type": "object",
            "description": "One image per story, shared by every language. kind photo = US federal government photo (public domain); card = drawn by Aurith for this story.",
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "photo",
                  "card"
                ]
              },
              "credit": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "alt": {
                "type": "string"
              },
              "width": {
                "type": "integer"
              },
              "height": {
                "type": "integer"
              }
            },
            "required": [
              "url",
              "kind",
              "credit",
              "alt",
              "width",
              "height"
            ]
          }
        }
      },
      "ItemsPage": {
        "type": "object",
        "required": [
          "items",
          "next",
          "held"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Item"
            }
          },
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last item id when page is full; null when short."
          },
          "held": {
            "type": "integer",
            "description": "Number of returned items."
          }
        }
      },
      "SampleResponse": {
        "type": "object",
        "required": [
          "category",
          "items",
          "held",
          "refreshed_at"
        ],
        "properties": {
          "category": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SampleItem"
            }
          },
          "held": {
            "type": "integer",
            "description": "Number of cached category items, up to 10."
          },
          "refreshed_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SampleItem": {
        "type": "object",
        "required": [
          "headline",
          "summary",
          "source_name",
          "written_at",
          "region"
        ],
        "properties": {
          "headline": {
            "type": "string"
          },
          "summary": {
            "type": "string",
            "maxLength": 400
          },
          "source_name": {
            "type": "string"
          },
          "written_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "KeyInfo": {
        "type": "object",
        "required": [
          "label",
          "tier",
          "per_minute",
          "rate",
          "languages",
          "sources",
          "packs",
          "releases",
          "settings",
          "expires_at"
        ],
        "properties": {
          "label": {
            "type": "string"
          },
          "tier": {
            "type": "string"
          },
          "per_minute": {
            "type": [
              "integer",
              "null"
            ]
          },
          "rate": {
            "type": "string"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "string",
                "const": "all"
              }
            ]
          },
          "packs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "releases": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "settings": {
            "type": "object",
            "additionalProperties": true
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "SettingsInput": {
        "type": "object",
        "properties": {
          "regions": {
            "$ref": "#/components/schemas/FilterValues"
          },
          "categories": {
            "$ref": "#/components/schemas/FilterValues"
          },
          "subjects": {
            "$ref": "#/components/schemas/FilterValues"
          },
          "sources": {
            "$ref": "#/components/schemas/FilterValues"
          },
          "languages": {
            "$ref": "#/components/schemas/FilterValues"
          },
          "releases": {
            "$ref": "#/components/schemas/FilterValues"
          }
        }
      },
      "FilterValues": {
        "oneOf": [
          {
            "type": "string",
            "description": "Comma-separated values."
          },
          {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          {
            "type": "null"
          }
        ]
      },
      "SettingsResponse": {
        "type": "object",
        "required": [
          "settings"
        ],
        "properties": {
          "settings": {
            "type": "object",
            "required": [
              "regions",
              "categories",
              "subjects",
              "sources",
              "languages",
              "releases"
            ],
            "properties": {
              "regions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "subjects": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sources": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "languages": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "releases": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "detail"
        ],
        "properties": {
          "detail": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid filter, category or setting.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or non-current API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Query parameter failed type validation.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit reached; wait the indicated seconds.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}