Skip to content

Sink REST API

Every Sink instance publishes its own OpenAPI documents:

  • https://your-domain/_docs/openapi.json — machine-readable OpenAPI JSON
  • https://your-domain/_docs/scalar — Scalar interactive reference
  • https://your-domain/_docs/swagger — Swagger UI

The public demo is explicitly available at https://sink.cool/_docs/scalar. Its schema documents the demo instance; use your own hostname for a self-hosted instance.

Authentication

API endpoints require the site token in the Authorization header:

http
Authorization: Bearer YOUR_SITE_TOKEN

The value is NUXT_SITE_TOKEN. When Cloudflare Access is configured, browser requests from an authenticated dashboard can instead use the signed Access application token supplied by Cloudflare. Sink verifies its signature, issuer, audience, and expiration.

Endpoints

MethodEndpointDescription
POST/api/link/createCreate a short link.
PUT/api/link/editUpdate an existing link.
POST/api/link/upsertReturn the existing link for a slug, or create it when absent.
POST/api/link/deleteDelete a link.
GET/api/link/queryGet one link by slug.
GET/api/link/searchSearch links.
GET/api/link/listList links with cursor pagination.
GET/api/link/exportExport a page of links as JSON. Follow cursor until list_complete is true.
POST/api/link/importImport a validated batch. Existing active slugs are skipped.
POST/api/link/checkCheck up to ten stored links' target availability, with a 1–30 second timeout.
GET/api/link/aiGenerate an AI slug suggestion.
GET/api/link/og-aiGenerate AI OpenGraph metadata.

Analytics and logs

MethodEndpointDescription
GET/api/stats/countersGet analytics counters.
GET/api/stats/metricsGet detailed metrics by dimension.
GET/api/stats/viewsGet time-series view counts.
GET/api/stats/heatmapGet heatmap data.
GET/api/stats/exportExport access analytics as CSV. This is the actual public URL handled by the dynamic stats action route.
GET/api/logs/eventsGet recent event logs.
GET/api/logs/locationsGet recent access locations.

Migration and utilities

MethodEndpointDescription
GET/api/link/migration/statusReport whether the KV-to-D1 migration marker exists and return it.
POST/api/link/migration/runCopy one bounded page (up to 40 KV records) into D1. Continue with the opaque response cursor until completed is true.
GET/api/verifyVerify credentials and report site-token or cloudflare-access.
GET/api/locationReturn request latitude and longitude from Cloudflare request metadata.
POST/api/upload/imageUpload an OpenGraph image to R2.
POST/api/backupTrigger a manual KV compatibility backup to R2.

D1 is authoritative. KV serves as the write-through read cache and the temporary source of legacy links until the migration marker is written. Migration never overwrites an existing D1 row. force=true rescans KV without changing that rule. If a migration page returns failures, resolve them and start a new run because that run is discarded.

http
POST /api/link/create
Authorization: Bearer SinkCool
Content-Type: application/json

{
  "url": "https://github.com/miantiao-me/Sink",
  "slug": "sink",
  "comment": "GitHub repository",
  "expiration": 1767225599,
  "apple": "https://apps.apple.com/app/id6745417598",
  "google": "https://play.google.com/store/apps/details?id=com.example",
  "geo": { "US": "https://example.com/us" },
  "title": "Sink - Link Shortener",
  "description": "A simple, speedy, secure link shortener",
  "image": "/_assets/images/sink/cover.webp",
  "password": "correct-horse-battery-staple",
  "unsafe": false,
  "redirectWithQuery": true
}
FieldTypeRequiredDescription
urlstringYesTarget URL, up to 2048 characters.
slugstringNoCustom slug; generated when omitted.
commentstringNoInternal note.
expirationnumberNoFuture Unix timestamp in seconds.
apple / googlestringNoDevice-specific target URL.
geoobjectNoCountry-code-to-URL routing map.
title / description / imagestringNoOpenGraph metadata.
cloakingbooleanNoLoad the destination in an iframe while keeping the short URL visible.
redirectWithQuerybooleanNoPer-link query forwarding override.
passwordstringNoPassword protection; stored as a hash.
unsafebooleanNoRequire an unsafe-link confirmation page.

Geo routing uses Cloudflare's two-letter country code. Device routing takes precedence over the default or geo target. Programmatic visitors can pass x-link-password for protected links and x-link-confirm: true after approving unsafe destinations.

Export analytics

http
GET /api/stats/export?startAt=1717200000&endAt=1719791999&slug=sink
Authorization: Bearer SinkCool

The response is text/csv with slug, url, viewer, views, and referer columns. Filters accepted by analytics views, including time, slug, country, browser, and device filters, can be supplied as query parameters.

Import and export behavior

Link export uses pages of NUXT_PUBLIC_KV_BATCH_LIMIT (default 50). Import accepts half that value (default 25) per request. That bounded API contract remains for compatibility and request cost control; it should not be interpreted as two KV operations per link. Link creation and duplicate detection are authoritative D1 operations, followed by a best-effort write-through KV cache update. Expired imports are rejected and duplicates are skipped. Export preserves password hashes in Sink's portable storage format so they can be imported unchanged; dashboard-masked values cannot be imported as plaintext passwords.

CORS

Set NUXT_API_CORS=true at build time to enable CORS for /api/**.