Changelog

Per-API-change history. Each entry corresponds to a deployment that touched the public API surface. For the broader project roadmap and shipped features, see /backlogs.

2026-10-11 — Redirect links, live content + reserved slugs

ADDITIVE
  • POST /api/v1/links — new body shape {"type": "redirect", "redirect_url", "slug"?, "label"?, "tags"?, "expires_at"?}: creates a link that answers 302 to redirect_url (https only, no credentials, default port, host on the environment allowlist). The file body is unchanged and stays the default when type is absent
  • POST /api/v1/links — optional slug on redirect bodies, accepted only from API consumers OCPL has enabled for a slug prefix: wf- followed by 8 lowercase hex characters (11 characters in all)
  • DELETE /api/v1/links/{slug}, PATCH /api/v1/links/{slug}/tags and DELETE /api/v1/links/{slug}/tags/{tag} — the slug path parameter also accepts the 11-character wf- form
  • Error codes: invalid_redirect_url, redirect_host_not_allowed, invalid_slug, slug_not_allowed, slug_taken. Error bodies may carry an optional field naming the offending request field
  • POST /api/v1/links — new body shape {"type": "dynamic", "api_url", "slug"?, "label"?, "tags"?, "expires_at"?}: a live content link. At request time the short URL fetches api_url (https, default port, a domain name on the upstream allowlist, with the upstream token of your consumer) and streams the answer back
  • Public GET on a live content link — only a 200 is shown, streamed with just Content-Type, Content-Disposition and Content-Length from the upstream (25 MB cap, 10 s to the response headers, never cached, redirects never followed); only PDF, PNG, JPEG, GIF, WebP, plain text and JSON are shown in the browser, any other type (HTML and SVG included) is always sent as a download; upstream 404 and 410 show the branded pages, anything else a temporarily-unavailable page (502 or 504)
  • Error codes: invalid_api_url, api_host_not_allowed, no_upstream_token
  • expires_at — link expiry is not implemented, so links never expire. Redirect and live content bodies accept "expires_at": null (it means never expires and changes nothing, like leaving it out); any other value returns 422 invalid_field until expiry exists. File bodies do not accept expires_at

2026-05-05 — Plan 4c · Public docs + OpenAPI

ADDITIVE
  • GET /api/v1/openapi.json (new — public, no auth, 5-min edge cache)
  • Internal: every /api/v1 parser refactored onto src/schema/* (byte-identical behaviour; no caller-visible changes)

2026-05-04 — Plan 4b · Tags

ADDITIVE
  • POST /api/v1/links — body field tags?: string[] (max 20, charset [a-z0-9:_-])
  • POST /api/v1/links — response field tags: string[] (always present, [] if none)
  • PATCH /api/v1/links/{slug}/tags (new — additive, never removes)
  • DELETE /api/v1/links/{slug}/tags/{tag} (new)
  • Error codes: invalid_tag, too_many_tags

2026-04-25 — Plan 2 · API for create + revoke + webhooks

INITIAL RELEASE
  • POST /api/v1/links (create)
  • DELETE /api/v1/links/{slug} (revoke)
  • Idempotency-Key support (24h TTL, conflict detection)
  • Webhook delivery on revoke (5s timeout, fire-and-forget)