# Shark Track API Short links on shrk.in with per-click analytics. Full spec: https://docs.shrk.in/openapi.json Base URL: https://app.shrk.in/v1 Auth: Authorization: Bearer stk_live_... (create keys in the dashboard, Settings > API keys) Scopes: read keys allow GET only; write keys allow POST, PATCH and DELETE. Timestamps are epoch milliseconds. ## Two things that surprise people 1. Tracking parameters never appear in the short link. You set them on the link; the redirect applies them to the destination and records them per click. So the URL you share is always the bare https://shrk.in/. 2. Any utm_ keys on a destination you submit are lifted into the link's params automatically. Other query parameters, like id=482, stay on the destination. ## Errors Every error is {"error": {"code", "message", "field"}}. Branch on code, not message. Common codes: key_missing, key_unknown, key_revoked, key_expired, insufficient_scope, invalid_request, not_found, conflict, delete_requires_dashboard, rate_limited. Rate limit: 600 requests per minute per key. Responses carry X-RateLimit-Remaining. ## Endpoints GET /me who this key belongs to, and its scope GET /links ?q= &tag= &project= &status= &sort= &limit= &cursor= POST /links {destination, slug?, title?, tags?, params?, projectId?, expiresAt?, clickLimit?} GET /links/{id} PATCH /links/{id} any subset of the create fields, plus status DELETE /links/{id} only if the link has no human clicks (see below) GET /links/{id}/stats ?range=today|24h|7d|30d|90d|all &bots=0|1 GET /links/{id}/clicks individual clicks, paginated GET /links/{id}/export.csv raw clicks as CSV GET /links/{id}/qr.svg ?size=512 &variant= GET /links/{id}/variants POST /links/{id}/variants {code, params?} PATCH /links/{id}/variants/{variantId} DELETE /links/{id}/variants/{variantId} POST /links/bulk/preview {text} parse without creating POST /links/bulk/create {text, tags?, projectId?, params?} POST /links/bulk/params {ids, params, mode} mode: merge | replace POST /links/bulk/project {ids, projectId} null unfiles GET /links/tags GET /projects POST /projects {name} PATCH /projects/{id} {name} DELETE /projects/{id} links inside are unfiled, not deleted GET /workspace GET /workspace/stats same range options as link stats ## Delete is restricted on purpose DELETE /links/{id} works only when the link has no recorded human clicks. A link with clicks returns 409 and code delete_requires_dashboard. Archive it instead: PATCH /links/{id} {"status": "archived"} Archiving hides a link from the list while keeping it working and keeping its history. Bot-only links are still deletable, because bots are excluded from click counts everywhere in the product. Custom slugs, and renaming a slug, require a superuser key. Omit slug on create to get a generated one. ## Variants A variant is a second address to the same destination: https://shrk.in/sale/email. Its params layer over the link's own. Use them to tell channels or printed placements apart while sharing one destination. A code that matches no variant still redirects to the base link and is reported as an unrecognised code. ## Examples Create a link, letting the UTMs be lifted out of the URL: curl -sX POST https://app.shrk.in/v1/links \ -H "Authorization: Bearer $SHARK_TRACK_KEY" \ -H "content-type: application/json" \ -d '{"destination":"https://example.com/sale?utm_source=newsletter","title":"Autumn sale"}' Add a variant for one channel: curl -sX POST https://app.shrk.in/v1/links/lnk_123/variants \ -H "Authorization: Bearer $SHARK_TRACK_KEY" \ -H "content-type: application/json" \ -d '{"code":"x","params":{"utm_source":"x","utm_medium":"social"}}' Read last week's numbers: curl -s "https://app.shrk.in/v1/links/lnk_123/stats?range=7d" \ -H "Authorization: Bearer $SHARK_TRACK_KEY" Create fifty links from a list: curl -sX POST https://app.shrk.in/v1/links/bulk/create \ -H "Authorization: Bearer $SHARK_TRACK_KEY" \ -H "content-type: application/json" \ -d '{"text":"https://a.com\nhttps://b.com","tags":["q4"]}'