openapi: 3.0.3 info: title: Android Cast URL Shortener description: | Standalone service at https://s.f0xx.org — SPEC docs/specs/20100611_3_url_shortener.md version: 0.1.0 servers: - url: https://s.f0xx.org description: Production (FE TLS → BE :80) paths: /api/v1/health: get: summary: Service health operationId: health responses: '200': description: OK content: application/json: schema: type: object properties: status: type: string service: type: string /api/v1/shorten: get: summary: Shorten URL (query params) description: Prefer POST for long URLs or sensitive query strings. operationId: shortenGet parameters: - $ref: '#/components/parameters/url' - $ref: '#/components/parameters/bearer' - $ref: '#/components/parameters/signer' - $ref: '#/components/parameters/ttl' responses: '200': $ref: '#/components/responses/ShortenSuccess' '400': $ref: '#/components/responses/ShortenError' '401': $ref: '#/components/responses/ShortenError' '403': $ref: '#/components/responses/ShortenError' '409': $ref: '#/components/responses/ShortenError' '429': $ref: '#/components/responses/ShortenError' post: summary: Shorten URL (JSON body) — preferred operationId: shortenPost requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShortenRequest' responses: '200': $ref: '#/components/responses/ShortenSuccess' '400': $ref: '#/components/responses/ShortenError' '401': $ref: '#/components/responses/ShortenError' '403': $ref: '#/components/responses/ShortenError' '409': $ref: '#/components/responses/ShortenError' '429': $ref: '#/components/responses/ShortenError' /api/v1/qr/{slug}.png: get: summary: QR code PNG for short URL operationId: qrPng parameters: - name: slug in: path required: true schema: type: string pattern: '^[a-f0-9]{6,16}$' - name: size in: query schema: type: integer default: 256 minimum: 128 maximum: 512 responses: '200': description: PNG image content: image/png: schema: type: string format: binary '404': description: Unknown slug '410': description: Expired slug /{slug}: get: summary: Redirect to original URL operationId: redirect parameters: - name: slug in: path required: true schema: type: string pattern: '^[a-f0-9]{6,16}$' responses: '302': description: Found — Location is original URL headers: Location: schema: type: string format: uri '404': description: Unknown slug '410': description: Expired slug head: summary: Redirect probe (no body) operationId: redirectHead parameters: - name: slug in: path required: true schema: type: string responses: '302': description: Found '404': description: Unknown '410': description: Expired components: parameters: url: name: url in: query required: true schema: type: string format: uri maxLength: 2048 bearer: name: bearer in: query required: true schema: type: string minLength: 8 signer: name: signer in: query required: false schema: type: string ttl: name: ttl in: query required: false description: Seconds until expiry; 0 = permanent (signer required) schema: type: integer minimum: 0 default: 86400 schemas: ShortenRequest: type: object required: [url, bearer] properties: url: type: string format: uri maxLength: 2048 bearer: type: string signer: type: string ttl: type: integer minimum: 0 default: 86400 ShortenSuccess: type: object required: [code, url, u, bearer, ttl] properties: code: type: string enum: ['0'] url: type: string format: uri u: type: string format: uri description: Short URL bearer: type: string ttl: type: string description: Remaining seconds as string; "0" if permanent qr: type: string format: uri description: Optional v1.1 — URL to QR PNG ShortenError: type: object required: [code, desc] properties: code: type: string enum: - ACCESS_DENIED - MISSING_BEARER - INVALID_URL - TTL_EXPIRED - SIGNER_REQUIRED - RATE_LIMITED - INTERNAL - NOT_IMPLEMENTED url: type: string bearer: type: string desc: type: string responses: ShortenSuccess: description: Short URL created or cached content: application/json: schema: $ref: '#/components/schemas/ShortenSuccess' ShortenError: description: Error content: application/json: schema: $ref: '#/components/schemas/ShortenError'