REST API
API overview
Authentication, errors, pagination, idempotency and rate limits.
The REST API controls what the dashboard shows: apps, domains, links, statistics, webhooks and the setup of item links. Every endpoint of the reference pages in this section is generated from the same OpenAPI file the API is tested against, so it cannot drift from the code.
Base URL: https://api.becklinks.com/v1
Authentication#
Send a secret key as a bearer token, from your server only:
curl https://api.becklinks.com/v1/links -H "Authorization: Bearer $SECRET_KEY"- Keys look like
sk_live_…andsk_test_…. Each belongs to one project environment: a test key reads and writes test data only. - A key has scopes. Every endpoint lists the scope it needs; a missing scope is
403 insufficient_scope. A publishable key (pk_…) is for apps and is rejected by the REST API. - Keys are stored as hashes and shown once. Create and revoke them in the dashboard.
| Scope | Allows |
|---|---|
links:read, links:write | Links, resource types, URL templates, the metadata API, invalidation |
campaigns:write | Campaigns |
analytics:read | Statistics and exports of statistics |
apps:write | The iOS, Android and web app settings |
domains:write | Domains |
webhooks:write | Webhook endpoints and deliveries |
attribution:read | Reading attribution |
events:write | Events sent from your server |
privacy:write | Deleting an end user's data (never implied by another scope) |
Errors#
Errors are application/problem+json (RFC 9457) with a
stable code. Branch on code, never on title or detail. A 422 validation_failed lists each
invalid field in errors[] with a JSON Pointer. Every response carries X-Request-Id; quote it when you
contact support. All codes are in the error reference.
{
"type": "https://docs.becklinks.com/errors/validation_failed",
"title": "Request validation failed",
"status": 422,
"code": "validation_failed",
"errors": [{ "in": "body", "pointer": "/key", "code": "invalid_format", "detail": "…" }],
"request_id": "01JA2B3C4D5E6F7G8H9J0K1M2N"
}Idempotency#
Send an Idempotency-Key header (a UUID, 8 to 255 printable ASCII characters) on a POST to make a
retry safe. Within 24 hours the original response is returned with Idempotent-Replayed: true. The same
key with a different request is 422 idempotency_key_reused; while the first request still runs it
is 409 idempotency_key_in_progress. Server errors and rejected input are not stored, so those can be
retried with the same key.
Rate limits#
Limits apply per client IP and per secret key (token buckets with a burst). Every response carries
RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds) for the limit closest to
running out. Over a limit you get 429 rate_limited with Retry-After in seconds; wait that long, then retry.
Pagination#
List endpoints take limit (1 to 100, default 25) and cursor, and return
{ "data": [...], "next_cursor": "…" }. next_cursor is null on the last page. Pass it back as cursor.
Conventions#
JSON keys are snake_case, ids are ULIDs (26 characters), timestamps are ISO 8601 in UTC (Z). New
response fields can appear at any time, so ignore fields you do not know. Breaking changes only come
with a new major version in the path.
The reference#
Pick a group in the navigation: Applications, Domains, Links, Stats, Resources (resource types, URL templates, the metadata API, previews and invalidation) and Webhooks. For the SDK endpoints your app calls with a publishable key, use the Flutter SDK; you do not call them by hand.
Verify it works#
curl -i https://api.becklinks.com/v1/links?limit=1 -H "Authorization: Bearer $SECRET_KEY"200 with data means the key and scope work. 401 invalid_api_key means the key is wrong, expired or
revoked; 403 insufficient_scope means it lacks links:read.