{"openapi":"3.1.0","info":{"title":"IPForge API","version":"1.1.0","x-commit":"048c43d","summary":"Owner-authorized link and SVG tracking — create links and tracking images, read what real activity happened on them, add custom domains, buy credits.","description":"Every operation is authenticated with an owner API token: `Authorization: Bearer ipf_…`. A human issues the token once in the dashboard (Settings → API tokens) and chooses its scopes; everything after is machine-only.\n\nScopes: `links:read` — List your links and read their summaries; `links:write` — Create, edit, pause and delete links; `captures:summary` — Deduplicated, human-only activity summaries per link; `captures:raw` — Raw visitor records: IP address, geo/ASN, fingerprint, device, behavior; `domains:read` — List your custom domains and their verification status; `domains:write` — Add and remove custom domains; `credits:read` — Read your credit balance and payment history; `credits:write` — Create USDC orders to buy credits.\nA token issued without an explicit scope list holds: `links:read`, `links:write`, `captures:summary`, `domains:read`, `domains:write`, `credits:read`, `credits:write` — never `captures:raw`.\nThe product has two tracking assets, links and SVG images, and the same scopes cover both: `links:read` / `links:write` list, read, create and delete SVGs as they do links, and `captures:summary` / `captures:raw` read an SVG's visitors as they read a link's. A scope names what data a token may see, not which table it lives in.\n\nVisitor data is summarized by default: deduplicated, human-only, with device/OS/browser family and country — never an IP address, ASN, fingerprint, user-agent string, wallet or behavior record. The owner may grant `captures:raw` per token to read what the dashboard shows.\n\nEvery error is `{ \"error\": { \"code\", \"message\", \"field\"?, \"request_id\" } }` with a code from the closed `ErrorCode` set — switch on `code`, never on `message`. Every response carries `X-Request-Id` and `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset`; a 429 adds `Retry-After`.\n\nResources are owner-scoped server-side: another owner's id, a deleted resource and a made-up id are the same 404."},"servers":[{"url":"https://ipforge.xyz"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"meta","description":"Token introspection."},{"name":"activity","description":"Start here: what real activity happened across every link and SVG, in one bounded answer (summary projection only)."},{"name":"links","description":"Tracking links. Creating one costs credits; every link response carries its resolved public `url`."},{"name":"svgs","description":"Tracking SVG images — the product's second asset, under the same scopes and the same contract as links. Creating one costs credits; every SVG response carries the resolved public `url` it is served at (`/s/{shortId}`, `image/svg+xml`)."},{"name":"captures","description":"What happened on a link or an SVG — one projection for both: summaries by default, raw behind `captures:raw`."},{"name":"domains","description":"Custom domains: add → set the A + TXT records → check → verified."},{"name":"credits","description":"The balance."},{"name":"orders","description":"Buying credits with USDC on Solana, Polygon or BNB Chain: create an order, pay from any wallet, verify with the transaction hash."}],"paths":{"/api/v1/activity":{"get":{"operationId":"getActivityOverview","tags":["activity"],"summary":"What real activity happened, across every link and SVG","description":"Start here. One call: every live asset of the owner — links and SVGs — with its deduplicated real visitors, capture count, last real visit and top countries / device families in the window (`since`, default the last 30 days), most recently active first, at most 100 assets. Summary projection only. Then read one asset's visitors with its `captures` operation.","security":[{"bearerAuth":["links:read","captures:summary"]}],"x-scopes":["links:read","captures:summary"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"since","in":"query","required":false,"description":"Start of the window (ISO 8601). Omitted → the last 30 days.","schema":{"type":"string","format":"date-time"}},{"name":"kind","in":"query","required":false,"description":"Only links, or only SVGs. Omitted → both.","schema":{"type":"string","enum":["link","svg"]}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityOverview"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]}},"/api/v1/credits":{"get":{"operationId":"getCredits","tags":["credits"],"summary":"Your credit balance","security":[{"bearerAuth":["credits:read"]}],"x-scopes":["credits:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]}},"/api/v1/domains":{"get":{"operationId":"listDomains","tags":["domains"],"summary":"List your custom domains","security":[{"bearerAuth":["domains:read"]}],"x-scopes":["domains:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainList"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]},"post":{"operationId":"createDomain","tags":["domains"],"summary":"Add a custom domain","description":"Returns the exact A and TXT records to set. The domain is `pending` until `check` sees both; a pending domain can obtain a TLS certificate only within `tls.pendingWindowEndsAt` and `tls.asksBudget`.","security":[{"bearerAuth":["domains:write"]}],"x-scopes":["domains:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"description":"A hostname you control, lowercase, e.g. `go.example.com`.","type":"string","minLength":1,"maxLength":253}},"required":["domain"]}}}},"responses":{"201":{"description":"Created","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.\n\n`domain_not_allowed` — The domain cannot be added as a custom domain: it is a system domain or already managed by the platform (`field`: domain).\n\n`domain_limit_reached` — The owner already has the maximum number of custom domains.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request","domain_not_allowed","domain_limit_reached"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"409":{"description":"`domain_taken` — The domain is already registered as a custom domain.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["domain_taken"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","domain_not_allowed","domain_limit_reached","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","domain_taken","rate_limited","internal_error"]}},"/api/v1/domains/{id}":{"delete":{"operationId":"deleteDomain","tags":["domains"],"summary":"Remove a custom domain","description":"Links on the domain are deactivated.","security":[{"bearerAuth":["domains:write"]}],"x-scopes":["domains:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]},"get":{"operationId":"getDomain","tags":["domains"],"summary":"Get one custom domain","security":[{"bearerAuth":["domains:read"]}],"x-scopes":["domains:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}},"/api/v1/domains/{id}/check":{"post":{"operationId":"checkDomain","tags":["domains"],"summary":"Check a domain's DNS and update its status","description":"Resolves the A record (must include the platform IP) and the `_ipforge-verify` TXT record (must equal the token). Both present → `verified`; otherwise `failed` (re-check any time).","security":[{"bearerAuth":["domains:write"]}],"x-scopes":["domains:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCheck"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}},"/api/v1/links":{"get":{"operationId":"listLinks","tags":["links"],"summary":"List your links","security":[{"bearerAuth":["links:read"]}],"x-scopes":["links:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkPage"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]},"post":{"operationId":"createLink","tags":["links"],"summary":"Create a link","description":"Costs credits (base 1, more for advanced tracking / smart routing / paid templates). The response carries the resolved public `url`.","security":[{"bearerAuth":["links:write"]}],"x-scopes":["links:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"type":{"type":"string","enum":["redirect","landing"]},"redirectUrl":{"description":"Required when type is `redirect`.","type":"string","format":"uri"},"domain":{"description":"A platform domain (`ipforge.xyz`, `brokolli.xyz`, `tarology.xyz`) or one of the owner's verified custom domains; omitted → `ipforge.xyz`.","type":"string"},"customSlug":{"description":"The path the link is served at on its domain. Omitted → a generated content-shaped slug.","type":"string","minLength":1,"maxLength":100,"pattern":"^[a-z0-9]([a-z0-9\\-\\/]*[a-z0-9])?$"},"urlExtension":{"type":"string"},"autoContentSlug":{"description":"Append a random suffix to `customSlug` so repeated creates never collide.","type":"boolean"},"behaviorTrackingEnabled":{"type":"boolean"},"trackingConfig":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}},"smartRouting":{"anyOf":[{"type":"object","properties":{"rules":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","minLength":1},"conditions":{"minItems":1,"type":"array","items":{"anyOf":[{"type":"object","properties":{"type":{"type":"string","const":"device"},"values":{"minItems":1,"type":"array","items":{"type":"string","enum":["mobile","desktop","tablet"]}}},"required":["type","values"]},{"type":"object","properties":{"type":{"type":"string","const":"country"},"values":{"minItems":1,"type":"array","items":{"type":"string","minLength":2,"maxLength":2}}},"required":["type","values"]},{"type":"object","properties":{"type":{"type":"string","const":"browser"},"values":{"minItems":1,"type":"array","items":{"type":"string"}}},"required":["type","values"]},{"type":"object","properties":{"type":{"type":"string","const":"os"},"values":{"minItems":1,"type":"array","items":{"type":"string"}}},"required":["type","values"]},{"type":"object","properties":{"type":{"type":"string","const":"time"},"startUtc":{"type":"string","pattern":"^\\d{2}:\\d{2}$"},"endUtc":{"type":"string","pattern":"^\\d{2}:\\d{2}$"}},"required":["type","startUtc","endUtc"]}]}},"destinationUrl":{"type":"string","format":"uri"}},"required":["id","name","conditions","destinationUrl"]}},"expiration":{"type":"object","properties":{"expiresAt":{"type":"string","format":"date-time"},"fallbackUrl":{"type":"string","format":"uri"}},"required":["expiresAt"]},"abTest":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string","minLength":1},"url":{"type":"string","format":"uri"},"weight":{"type":"number","minimum":0,"maximum":100}},"required":["id","name","url","weight"]}}}},{"type":"null"}]},"landingPage":{"type":"object","properties":{"name":{"type":"string","minLength":1},"templateId":{"type":"string"},"customHtml":{"type":"string"},"customCss":{"type":"string"},"configJson":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name"]}},"required":["title","type"]}}}},"responses":{"201":{"description":"Created","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkResponse"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.\n\n`slug_reserved` — The requested custom slug is a reserved path (`field`: customSlug).\n\n`domain_not_available` — The requested link domain is neither a system domain nor one of the owner's verified custom domains (`field`: domain).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request","slug_reserved","domain_not_available"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"402":{"description":"`insufficient_credits` — The account lacks the credits this operation costs. `required` and `available` carry the numbers.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["insufficient_credits"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"409":{"description":"`slug_taken` — The custom slug is already used on that domain (`field`: customSlug).","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["slug_taken"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","slug_reserved","domain_not_available","missing_token","invalid_token","token_revoked","token_expired","insufficient_credits","account_suspended","insufficient_scope","slug_taken","rate_limited","internal_error"]}},"/api/v1/links/{id}":{"delete":{"operationId":"deleteLink","tags":["links"],"summary":"Delete a link","description":"Soft-delete: the link stops resolving everywhere; its records are retained.","security":[{"bearerAuth":["links:write"]}],"x-scopes":["links:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]},"get":{"operationId":"getLink","tags":["links"],"summary":"Get one link","security":[{"bearerAuth":["links:read"]}],"x-scopes":["links:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LinkResponse"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}},"/api/v1/links/{id}/captures":{"get":{"operationId":"listLinkCaptures","tags":["captures"],"summary":"What real activity happened on a link","description":"Default `view=summary`: one row per deduplicated human/unknown visitor — class, device/OS/browser family, country, first/last seen, visit count; never an IP, ASN, fingerprint, user-agent, wallet or behavior record. `view=raw` returns the dashboard's capture rows and requires the `captures:raw` scope the owner grants per token. Newest first; page with `cursor`, poll with `since`.","security":[{"bearerAuth":["captures:summary"]},{"bearerAuth":["captures:raw"]}],"x-scopes":["captures:summary"],"x-alt-scopes":{"when":"view=raw","scopes":["captures:raw"]},"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","required":false,"description":"`summary` (default) needs `captures:summary`; `raw` needs the `captures:raw` scope the owner grants per token.","schema":{"default":"summary","type":"string","enum":["summary","raw"]}},{"name":"since","in":"query","required":false,"description":"Only visitors seen at or after this instant (summary: by last visit; raw: by capture time).","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapturesPage"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}},"/api/v1/orders":{"post":{"operationId":"createOrder","tags":["orders"],"summary":"Create a USDC order for credits","description":"Pick a bundle or a custom amount and a chain. Send exactly `pay.amountUsdc` USDC to `pay.to` on `pay.chain` within the hour, then verify with the transaction hash.","security":[{"bearerAuth":["credits:write"]}],"x-scopes":["credits:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"description":"A priced bundle (`starter` 13 / `pro` 50 / `bulk` 200 credits) or `custom` with `customCredits`.","anyOf":[{"type":"object","properties":{"bundleId":{"type":"string","enum":["starter","pro","bulk"]},"chain":{"type":"string","enum":["solana","polygon","bsc"]},"customCredits":{"not":{}}},"required":["bundleId","chain"]},{"type":"object","properties":{"bundleId":{"type":"string","const":"custom"},"chain":{"type":"string","enum":["solana","polygon","bsc"]},"customCredits":{"description":"1 USDC = 1 credit, 1–1000.","type":"integer","minimum":1,"maximum":1000}},"required":["bundleId","chain","customCredits"]}]}}}},"responses":{"201":{"description":"Created","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderResponse"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]}},"/api/v1/orders/{id}/verify":{"post":{"operationId":"verifyOrder","tags":["orders"],"summary":"Verify an order's payment and credit the account","description":"Checks the transaction on-chain against the order's amount and wallet. Success credits the account atomically. A failed verification can be retried with the correct hash. Limited to 10 attempts per 5 minutes.","security":[{"bearerAuth":["credits:write"]}],"x-scopes":["credits:write"],"x-rate-limit":{"limit":10,"windowSeconds":300},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"txHash":{"description":"The USDC transfer's transaction hash / signature on the order's chain.","type":"string","minLength":10,"maxLength":128}},"required":["txHash"]}}}},"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderVerified"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.\n\n`order_not_payable` — The order is not in a state that accepts a payment (already confirmed, or currently verifying). `status` carries the order's state.\n\n`payment_verification_failed` — The transaction did not verify on-chain for this order. `reason` says why; the order can be retried with a correct hash.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request","order_not_payable","payment_verification_failed"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"409":{"description":"`tx_hash_used` — The transaction hash already paid for a different order.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["tx_hash_used"]},"410":{"description":"`order_expired` — The order's payment window has closed; create a new order.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["order_expired"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","order_not_payable","payment_verification_failed","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","tx_hash_used","order_expired","rate_limited","internal_error"]}},"/api/v1/ping":{"get":{"operationId":"ping","tags":["meta"],"summary":"Who am I, what may I do","description":"The smallest proof a token works: its owner id and its own scopes. Requires no scope.","security":[{"bearerAuth":[]}],"x-scopes":[],"x-rate-limit":{"limit":120,"windowSeconds":60},"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ping"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","rate_limited","internal_error"]}},"/api/v1/svgs":{"get":{"operationId":"listSvgs","tags":["svgs"],"summary":"List your SVGs","description":"Your tracking SVG images, newest first, each with its resolved public `url` and its capture stats. The `links:read` scope covers SVGs: a scope names what data a token may see, not which table it lives in.","security":[{"bearerAuth":["links:read"]}],"x-scopes":["links:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SvgPage"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","rate_limited","internal_error"]},"post":{"operationId":"createSvg","tags":["svgs"],"summary":"Create an SVG","description":"Costs credits (base 1, one more per enabled advanced tracking group) — the same rule as the dashboard. The response carries the resolved public `url` the image is served at.","security":[{"bearerAuth":["links:write"]}],"x-scopes":["links:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":200},"template":{"description":"What the image renders as: a document, an image placeholder, a chart, a blank canvas or an invoice.","type":"string","enum":["document","image","chart","blank","invoice"]},"redirectUrl":{"description":"Where a click on the rendered SVG goes. Omitted → the image is not clickable.","type":"string","format":"uri"},"trackingConfig":{"description":"Collector toggles by id (canvas, webgl, audio, fonts, navigator, screen, timezone, …). Omitted → the SVG defaults. Each enabled `Extensions`/`Advanced` group costs one more credit.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"boolean"}}},"required":["title","template"]}}}},"responses":{"201":{"description":"Created","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SvgResponse"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"402":{"description":"`insufficient_credits` — The account lacks the credits this operation costs. `required` and `available` carry the numbers.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["insufficient_credits"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","insufficient_credits","account_suspended","insufficient_scope","rate_limited","internal_error"]}},"/api/v1/svgs/{id}":{"delete":{"operationId":"deleteSvg","tags":["svgs"],"summary":"Delete an SVG","description":"Soft-delete: the image stops resolving everywhere; its records are retained.","security":[{"bearerAuth":["links:write"]}],"x-scopes":["links:write"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]},"get":{"operationId":"getSvg","tags":["svgs"],"summary":"Get one SVG","security":[{"bearerAuth":["links:read"]}],"x-scopes":["links:read"],"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SvgResponse"}}}},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}},"/api/v1/svgs/{id}/captures":{"get":{"operationId":"listSvgCaptures","tags":["captures"],"summary":"What real activity happened on an SVG","description":"The same projection as a link's captures. Default `view=summary`: one row per deduplicated human/unknown visitor — class, device/OS/browser family, country, first/last seen, visit count; never an IP, ASN, fingerprint, user-agent, wallet or behavior record. `view=raw` returns the dashboard's capture rows and requires the `captures:raw` scope the owner grants per token. Newest first; page with `cursor`, poll with `since`.","security":[{"bearerAuth":["captures:summary"]},{"bearerAuth":["captures:raw"]}],"x-scopes":["captures:summary"],"x-alt-scopes":{"when":"view=raw","scopes":["captures:raw"]},"x-rate-limit":{"limit":120,"windowSeconds":60},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"view","in":"query","required":false,"description":"`summary` (default) needs `captures:summary`; `raw` needs the `captures:raw` scope the owner grants per token.","schema":{"default":"summary","type":"string","enum":["summary","raw"]}},{"name":"since","in":"query","required":false,"description":"Only visitors seen at or after this instant (summary: by last visit; raw: by capture time).","schema":{"type":"string","format":"date-time"}},{"name":"cursor","in":"query","required":false,"description":"`next_cursor` from the previous page.","schema":{"type":"string","minLength":1}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"OK","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CapturesPage"}}}},"400":{"description":"`invalid_request` — The body or query did not validate. `field` names the offending property when there is one.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["invalid_request"]},"401":{"description":"`missing_token` — No `Authorization: Bearer ipf_…` header was presented.\n\n`invalid_token` — The token is unknown.\n\n`token_revoked` — The token was revoked by its owner.\n\n`token_expired` — The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["missing_token","invalid_token","token_revoked","token_expired"]},"403":{"description":"`account_suspended` — The token's owner account is suspended.\n\n`insufficient_scope` — The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["account_suspended","insufficient_scope"]},"404":{"description":"`not_found` — No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["not_found"]},"429":{"description":"`rate_limited` — Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["rate_limited"]},"500":{"description":"`internal_error` — An unexpected server error. `request_id` identifies it in the server log.","headers":{"X-Request-Id":{"$ref":"#/components/headers/X-Request-Id"},"X-RateLimit-Limit":{"$ref":"#/components/headers/X-RateLimit-Limit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/X-RateLimit-Remaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/X-RateLimit-Reset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"x-error-codes":["internal_error"]}},"x-error-codes":["invalid_request","missing_token","invalid_token","token_revoked","token_expired","account_suspended","insufficient_scope","not_found","rate_limited","internal_error"]}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"ipf_<43 base62 chars>","description":"An owner API token, issued in the dashboard. The operation's security requirement lists the scopes the token must hold; a token lacking one is a 403 `insufficient_scope` naming what is missing."}},"schemas":{"ActivityAsset":{"description":"One asset's activity in the window — summary projection only: counts, families, countries; never a visitor record.","type":"object","properties":{"kind":{"type":"string","enum":["link","svg"]},"id":{"description":"Use it as `{ kind, id }` with `list_real_capture_summaries` / `compare_device_profiles` (MCP), or with the asset's own `captures` operation.","type":"string"},"title":{"type":"string"},"url":{"description":"The asset's resolved public URL.","type":"string","format":"uri"},"realVisitors":{"description":"Deduplicated human/unknown visitors in the window — the same rule as a capture summary row. Image proxies are NOT counted here; see `proxyRenders`.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"proxyRenders":{"description":"Fetches by a caching image proxy in the window — a floor on how often the asset was displayed somewhere, never a number of people. Read it with the overview's `reading`.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastProxyRenderAt":{"description":"The latest proxy render in the window, or null. Use it for \"rendered as recently as …\".","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"captures":{"description":"Every capture in the window, bots, proxies and duplicates included.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lastSeenAt":{"description":"The latest real visit in the window, or null when there was none.","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"topCountries":{"description":"Up to 3, by deduplicated visitors; visitors without a known country are not listed.","maxItems":3,"type":"array","items":{"type":"object","properties":{"country":{"description":"ISO 3166-1 alpha-2.","type":"string"},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["country","visitors"],"additionalProperties":false}},"topDevices":{"description":"Up to 3 device families, by deduplicated visitors.","maxItems":3,"type":"array","items":{"type":"object","properties":{"device":{"type":"string","enum":["desktop","mobile","tablet","unknown"]},"visitors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["device","visitors"],"additionalProperties":false}}},"required":["kind","id","title","url","realVisitors","proxyRenders","lastProxyRenderAt","captures","lastSeenAt","topCountries","topDevices"],"additionalProperties":false},"ActivityOverview":{"description":"What real activity happened, across every link and SVG, in one answer. Window default 30 days; at most 100 assets, 3 countries and devices each. Summary projection only.","type":"object","properties":{"since":{"description":"The window's start, as applied.","type":"string","format":"date-time"},"kind":{"description":"The kind filter applied, or null for both.","anyOf":[{"type":"string","enum":["link","svg"]},{"type":"null"}]},"assets":{"description":"Every live asset of the owner (with zeros when idle), most recently active first, at most 100.","maxItems":100,"type":"array","items":{"$ref":"#/components/schemas/ActivityAsset"}},"truncated":{"description":"true when the owner has more than 100 assets — the idle tail was cut; page them with the list operations.","type":"boolean"},"reading":{"description":"How to read every `proxyRenders` below. Quoted from the published measurement rules, not paraphrased.","type":"string","const":"A proxy render is a caching image proxy (GitHub Camo, the Gmail image proxy) fetching the asset for a reader we never see. N renders means the asset was displayed somewhere at least N times: one person refreshing five times and five people looking once produce the same number, and the proxy's cache removes renders from the count entirely. Read it as evidence of activity, never as an audience, a location or a device."},"totals":{"type":"object","properties":{"assets":{"description":"Live assets counted, before the cap.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"realVisitors":{"description":"Sum over the listed assets.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"proxyRenders":{"description":"Sum over the listed assets.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"captures":{"description":"Sum over the listed assets.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["assets","realVisitors","proxyRenders","captures"],"additionalProperties":false}},"required":["since","kind","assets","truncated","reading","totals"],"additionalProperties":false},"CaptureRaw":{"description":"The dashboard's capture record. Requires the `captures:raw` scope.","type":"object","properties":{"id":{"type":"string"},"linkId":{"description":"The link this capture belongs to, or null when it belongs to an SVG.","anyOf":[{"type":"string"},{"type":"null"}]},"svgId":{"description":"The SVG this capture belongs to, or null when it belongs to a link.","anyOf":[{"type":"string"},{"type":"null"}]},"ipAddress":{"anyOf":[{"type":"string"},{"type":"null"}]},"ipGeo":{"anyOf":[{"type":"object","properties":{"country":{"type":"string"},"countryCode":{"type":"string"},"city":{"type":"string"},"region":{"type":"string"},"isp":{"type":"string"},"asn":{"type":"string"},"asnOrganization":{"type":"string"},"isAnonymous":{"type":"boolean"},"isAnonymousProxy":{"type":"boolean"},"isAnonymousVpn":{"type":"boolean"},"isHostingProvider":{"type":"boolean"},"isPublicProxy":{"type":"boolean"},"isTorExitNode":{"type":"boolean"},"lat":{"type":"number"},"lng":{"type":"number"}},"additionalProperties":{}},{"type":"null"}]},"userAgent":{"anyOf":[{"type":"string"},{"type":"null"}]},"acceptLanguage":{"anyOf":[{"type":"string"},{"type":"null"}]},"referer":{"anyOf":[{"type":"string"},{"type":"null"}]},"fingerprint":{"description":"The full client fingerprint, including any detected wallets.","anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}]},"fingerprintHash":{"anyOf":[{"type":"string"},{"type":"null"}]},"deviceType":{"anyOf":[{"type":"string"},{"type":"null"}]},"os":{"anyOf":[{"type":"string"},{"type":"null"}]},"browser":{"anyOf":[{"type":"string"},{"type":"null"}]},"isDuplicate":{"type":"boolean"},"isOwner":{"type":"boolean"},"captureClass":{"type":"string","enum":["human","bot","unknown"]},"captureClassReason":{"type":"string"},"ipQuality":{"type":"string","enum":["residential","vpn","datacenter","tor","unknown"]},"ja4":{"anyOf":[{"type":"string"},{"type":"null"}]},"smartRouteMatch":{"anyOf":[{},{"type":"null"}]},"note":{"anyOf":[{"type":"string"},{"type":"null"}]},"tags":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"visitCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"behaviorSession":{"anyOf":[{"type":"object","properties":{"id":{"type":"string"},"captureId":{"type":"string"},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"},"visibleDwellMs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"engaged":{"type":"boolean"},"scrollDepthBucket":{"type":"string"},"ctaOutcome":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"}},"required":["id","captureId","firstSeenAt","lastSeenAt","visibleDwellMs","engaged","scrollDepthBucket","ctaOutcome","expiresAt"],"additionalProperties":{}},{"type":"null"}]}},"required":["id","linkId","svgId","ipAddress","ipGeo","userAgent","acceptLanguage","referer","fingerprint","fingerprintHash","deviceType","os","browser","isDuplicate","isOwner","captureClass","captureClassReason","ipQuality","ja4","smartRouteMatch","note","tags","createdAt","visitCount","behaviorSession"],"additionalProperties":false},"CapturesPage":{"description":"`summary` rows are deduplicated human-only visitors (default token). `raw` rows are the dashboard's capture records and need the `captures:raw` scope.","anyOf":[{"type":"object","properties":{"view":{"type":"string","const":"summary"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CaptureSummary"}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["view","items","next_cursor"],"additionalProperties":false},{"type":"object","properties":{"view":{"type":"string","const":"raw"},"items":{"type":"array","items":{"$ref":"#/components/schemas/CaptureRaw"}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["view","items","next_cursor"],"additionalProperties":false}]},"CaptureSummary":{"description":"One deduplicated human/unknown visitor — what a default token sees. Never an IP, ASN, ISP, city, coordinates, JA4, user-agent, fingerprint, wallet, behavior record or owner note.","type":"object","properties":{"id":{"description":"Id of this visitor's most recent capture. A handle for `since`-style follow-ups, not a visitor identifier.","type":"string"},"class":{"description":"`human` = a browser with no bot signal; `proxy` = a caching image proxy (GitHub Camo, the Gmail image proxy) fetched the asset for a reader we never see — a render, not a person, and never a location or a device; `unknown` = too few signals to say. Bots never appear.","type":"string","enum":["human","proxy","unknown"]},"hasDedupKey":{"description":"A device fingerprint hash exists, so this visitor's repeat visits were collapsed into this row.","type":"boolean"},"device":{"type":"string","enum":["desktop","mobile","tablet","unknown"]},"os":{"description":"Family only — `macOS`, `Windows`, `iOS`, `Android`, … — never a version.","anyOf":[{"type":"string"},{"type":"null"}]},"browser":{"description":"Family only — `Chrome`, `Safari`, … — never a version or the user-agent string.","anyOf":[{"type":"string"},{"type":"null"}]},"country":{"description":"ISO 3166-1 alpha-2 from IP geolocation. Never the city, region, coordinates, ISP or ASN.","anyOf":[{"type":"string"},{"type":"null"}]},"firstSeenAt":{"type":"string","format":"date-time"},"lastSeenAt":{"type":"string","format":"date-time"},"visits":{"description":"Captures collapsed into this row (1 when there is no dedup key).","type":"integer","minimum":1,"maximum":9007199254740991}},"required":["id","class","hasDedupKey","device","os","browser","country","firstSeenAt","lastSeenAt","visits"],"additionalProperties":false},"Credits":{"type":"object","properties":{"credits":{"description":"Credits available to spend. Creating a link costs at least 1.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["credits"],"additionalProperties":false},"Deleted":{"type":"object","properties":{"deleted":{"type":"boolean","const":true},"id":{"type":"string"}},"required":["deleted","id"],"additionalProperties":false},"DnsInstructions":{"description":"Set both records at the registrar, then call `check`.","type":"object","properties":{"aRecord":{"type":"object","properties":{"type":{"type":"string","const":"A"},"name":{"type":"string"},"value":{"type":"string"}},"required":["type","name","value"],"additionalProperties":false},"txtRecord":{"type":"object","properties":{"type":{"type":"string","const":"TXT"},"name":{"type":"string"},"value":{"type":"string"}},"required":["type","name","value"],"additionalProperties":false}},"required":["aRecord","txtRecord"],"additionalProperties":false},"Domain":{"type":"object","properties":{"id":{"type":"string"},"domain":{"type":"string"},"status":{"type":"string","enum":["pending","verified","failed"]},"verifiedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"},"instructions":{"$ref":"#/components/schemas/DnsInstructions"},"tls":{"$ref":"#/components/schemas/TlsState"}},"required":["id","domain","status","verifiedAt","createdAt","instructions","tls"],"additionalProperties":false},"DomainCheck":{"description":"The DNS check just performed and the domain's resulting state.","type":"object","properties":{"domain":{"$ref":"#/components/schemas/Domain"},"verified":{"type":"boolean"},"checks":{"type":"object","properties":{"aRecord":{"type":"boolean"},"txtRecord":{"type":"boolean"},"aRecordValues":{"type":"array","items":{"type":"string"}},"txtRecordValues":{"type":"array","items":{"type":"string"}}},"required":["aRecord","txtRecord","aRecordValues","txtRecordValues"],"additionalProperties":false}},"required":["domain","verified","checks"],"additionalProperties":false},"DomainList":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Domain"}}},"required":["items"],"additionalProperties":false},"DomainResponse":{"type":"object","properties":{"domain":{"$ref":"#/components/schemas/Domain"}},"required":["domain"],"additionalProperties":false},"Error":{"description":"The one error envelope every v1 route answers with.","type":"object","properties":{"error":{"description":"May carry extra machine-readable members per code: `required`/`available` (insufficient_credits), `required`/`missing` (insufficient_scope), `reason` (payment_verification_failed), `status` (order_not_payable).","type":"object","properties":{"code":{"$ref":"#/components/schemas/ErrorCode"},"message":{"description":"Human-readable; never parse it — switch on `code`.","type":"string"},"field":{"description":"The request property at fault, when there is one.","type":"string"},"request_id":{"description":"Quote it when reporting a problem; it is in the server log.","type":"string"}},"required":["code","message","request_id"],"additionalProperties":{}}},"required":["error"],"additionalProperties":false},"ErrorCode":{"description":"The closed set of v1 error codes.","type":"string","enum":["invalid_request","token_limit_reached","slug_reserved","domain_not_available","domain_not_allowed","domain_limit_reached","order_not_payable","payment_verification_failed","missing_token","invalid_token","token_revoked","token_expired","insufficient_credits","account_suspended","insufficient_scope","not_found","slug_taken","domain_taken","tx_hash_used","order_expired","rate_limited","internal_error"]},"Link":{"type":"object","properties":{"id":{"type":"string"},"shortId":{"type":"string"},"title":{"type":"string"},"type":{"type":"string","enum":["redirect","landing"]},"url":{"description":"The resolved public URL — share this; never compose it.","type":"string","format":"uri"},"domain":{"anyOf":[{"type":"string"},{"type":"null"}]},"customSlug":{"anyOf":[{"type":"string"},{"type":"null"}]},"redirectUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"isActive":{"type":"boolean"},"captureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/LinkStats"}},"required":["id","shortId","title","type","url","domain","customSlug","redirectUrl","isActive","captureCount","createdAt","stats"],"additionalProperties":false},"LinkPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Link"}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["items","next_cursor"],"additionalProperties":false},"LinkResponse":{"type":"object","properties":{"link":{"$ref":"#/components/schemas/Link"}},"required":["link"],"additionalProperties":false},"LinkStats":{"description":"Capture counts by class. The same shape for a link and for an SVG.","type":"object","properties":{"total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"real":{"description":"Human/unknown, not a proxy, not duplicate, not the owner — what `captures` lists in summary view.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"bot":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"proxy":{"description":"Fetches by a caching image proxy (GitHub Camo, the Gmail image proxy) — the asset was rendered somewhere at least this many times, by readers we never see. Never a count of people: `real` is the only visitor number.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"duplicate":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"owner":{"description":"The owner's own test visits.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["total","real","bot","proxy","duplicate","owner"],"additionalProperties":false},"Order":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","verifying","confirmed","expired","failed"]},"bundleId":{"type":"string","enum":["starter","pro","bulk","custom"]},"chain":{"type":"string","enum":["solana","polygon","bsc"]},"credits":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"amountUsdc":{"description":"Decimal string, e.g. `9.50`.","type":"string"},"txHash":{"anyOf":[{"type":"string"},{"type":"null"}]},"expiresAt":{"type":"string","format":"date-time"},"confirmedAt":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time"}},"required":["id","status","bundleId","chain","credits","amountUsdc","txHash","expiresAt","confirmedAt","createdAt"],"additionalProperties":false},"OrderResponse":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"pay":{"$ref":"#/components/schemas/PaymentInstructions"}},"required":["order","pay"],"additionalProperties":false},"OrderVerified":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"creditsAdded":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"credits":{"description":"The account balance after crediting.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["order","creditsAdded","credits"],"additionalProperties":false},"PaymentInstructions":{"description":"Everything a wallet needs. No browser signing: send, then verify with the tx hash.","type":"object","properties":{"amountUsdc":{"type":"string"},"currency":{"type":"string","const":"USDC"},"chain":{"type":"string","enum":["solana","polygon","bsc"]},"chainName":{"type":"string"},"to":{"description":"The receiving wallet. Send exactly `amountUsdc` of USDC on `chain`.","type":"string"},"usdcContract":{"description":"The USDC token mint/contract on that chain.","type":"string"},"decimals":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"expiresAt":{"type":"string","format":"date-time"},"verify":{"description":"Where to submit the transaction hash once sent.","type":"string"}},"required":["amountUsdc","currency","chain","chainName","to","usdcContract","decimals","expiresAt","verify"],"additionalProperties":false},"Ping":{"description":"Who am I, what may I do.","type":"object","properties":{"ok":{"type":"boolean","const":true},"owner":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"additionalProperties":false},"token":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}}},"required":["id","name","prefix","scopes"],"additionalProperties":false}},"required":["ok","owner","token"],"additionalProperties":false},"Svg":{"description":"A tracking SVG image. Same contract as a link: `url` resolved, `stats` attached, visitors never inlined.","type":"object","properties":{"id":{"type":"string"},"shortId":{"type":"string"},"title":{"type":"string"},"template":{"description":"What the image renders as.","type":"string","enum":["document","image","chart","blank","invoice"]},"url":{"description":"The resolved public URL of the image (`/s/{shortId}`) — embed or share this; never compose it.","type":"string","format":"uri"},"redirectUrl":{"description":"Where a click on the image goes, or null.","anyOf":[{"type":"string"},{"type":"null"}]},"isActive":{"type":"boolean"},"captureCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/LinkStats"}},"required":["id","shortId","title","template","url","redirectUrl","isActive","captureCount","createdAt","stats"],"additionalProperties":false},"SvgPage":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Svg"}},"next_cursor":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["items","next_cursor"],"additionalProperties":false},"SvgResponse":{"type":"object","properties":{"svg":{"$ref":"#/components/schemas/Svg"}},"required":["svg"],"additionalProperties":false},"TlsState":{"description":"The pending-TLS budget: verify before the window or the budget runs out.","type":"object","properties":{"certificateEligible":{"description":"Whether the edge may issue a TLS certificate for this domain right now.","type":"boolean"},"reason":{"type":"string","enum":["verified","pending_within_window","pending_window_expired","pending_budget_exhausted","failed"]},"pendingWindowEndsAt":{"description":"A pending domain may obtain a certificate only until this instant (72h after creation).","anyOf":[{"type":"string","format":"date-time"},{"type":"null"}]},"asksUsed":{"description":"Certificate asks spent inside the pending window; null when no counter exists yet.","anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"asksBudget":{"description":"Asks a pending domain may spend before verification.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["certificateEligible","reason","pendingWindowEndsAt","asksUsed","asksBudget"],"additionalProperties":false}},"headers":{"X-Request-Id":{"description":"Identifies this request in the server log; the same value is `error.request_id` on failures.","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Requests allowed per window for this token (default 120 per 60s).","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests left in the current window.","schema":{"type":"integer"}},"X-RateLimit-Reset":{"description":"Unix time (seconds) at which the window resets.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying (on 429).","schema":{"type":"integer"}}}},"x-scopes":{"links:read":"List your links and read their summaries","links:write":"Create, edit, pause and delete links","captures:summary":"Deduplicated, human-only activity summaries per link","captures:raw":"Raw visitor records: IP address, geo/ASN, fingerprint, device, behavior","domains:read":"List your custom domains and their verification status","domains:write":"Add and remove custom domains","credits:read":"Read your credit balance and payment history","credits:write":"Create USDC orders to buy credits"},"x-error-codes":{"invalid_request":{"status":400,"description":"The body or query did not validate. `field` names the offending property when there is one."},"token_limit_reached":{"status":400,"description":"The owner already holds the maximum number of active API tokens."},"slug_reserved":{"status":400,"description":"The requested custom slug is a reserved path (`field`: customSlug)."},"domain_not_available":{"status":400,"description":"The requested link domain is neither a system domain nor one of the owner's verified custom domains (`field`: domain)."},"domain_not_allowed":{"status":400,"description":"The domain cannot be added as a custom domain: it is a system domain or already managed by the platform (`field`: domain)."},"domain_limit_reached":{"status":400,"description":"The owner already has the maximum number of custom domains."},"order_not_payable":{"status":400,"description":"The order is not in a state that accepts a payment (already confirmed, or currently verifying). `status` carries the order's state."},"payment_verification_failed":{"status":400,"description":"The transaction did not verify on-chain for this order. `reason` says why; the order can be retried with a correct hash."},"missing_token":{"status":401,"description":"No `Authorization: Bearer ipf_…` header was presented."},"invalid_token":{"status":401,"description":"The token is unknown."},"token_revoked":{"status":401,"description":"The token was revoked by its owner."},"token_expired":{"status":401,"description":"The access token's lifetime is over (OAuth grants expire; refresh it at `/oauth/token`). A hand-issued `ipf_` token never expires."},"insufficient_credits":{"status":402,"description":"The account lacks the credits this operation costs. `required` and `available` carry the numbers."},"account_suspended":{"status":403,"description":"The token's owner account is suspended."},"insufficient_scope":{"status":403,"description":"The token lacks a scope this operation requires. `required` lists the scopes needed, `missing` the ones absent."},"not_found":{"status":404,"description":"No such resource for this owner. Another owner's id, a deleted resource and a made-up id are indistinguishable."},"slug_taken":{"status":409,"description":"The custom slug is already used on that domain (`field`: customSlug)."},"domain_taken":{"status":409,"description":"The domain is already registered as a custom domain."},"tx_hash_used":{"status":409,"description":"The transaction hash already paid for a different order."},"order_expired":{"status":410,"description":"The order's payment window has closed; create a new order."},"rate_limited":{"status":429,"description":"Too many requests for this token. `Retry-After` and `X-RateLimit-Reset` say when to try again."},"internal_error":{"status":500,"description":"An unexpected server error. `request_id` identifies it in the server log."}}}