Conventions¶
Behaviors shared by every endpoint in the Public API v2.
Identifiers¶
All resources are identified by a public_id — a stable, opaque string. Internal numeric IDs are never exposed. When creating relationships between resources (e.g., assigning content to a user), reference them by their public_id.
public_id values are UUIDs in canonical form. Treat them as opaque strings — do not parse or generate them yourself.
Pagination¶
List endpoints return a paginated envelope:
{
"data": [ /* up to page_size items */ ],
"meta": {
"page": 1,
"page_size": 25,
"total_count": 137
}
}
Control pagination with these query parameters:
| Parameter | Default | Max | Description |
|---|---|---|---|
page |
1 |
— | 1-indexed page number. |
page_size |
25 |
100 |
Items per page. |
A page below 1, or a page_size below 1 or above 100, returns 422 with a validation_error code. The API does not reduce an out-of-range value to the nearest allowed one.
Every list has a stable order: when two items tie on the sort field, a unique key breaks the tie. If the data does not change while you page, each item appears on exactly one page.
Sorting¶
List endpoints support ordering with a comma-separated list of fields. Prefix with - for descending order:
The supported fields per endpoint are listed in the API reference.
Filtering¶
Most list endpoints accept resource-specific filters as query parameters — for example GET /users?email=jane.doe@acme.com or GET /content-items?content_type=video&is_archived=false. The supported filters are listed in the API reference for each endpoint. See Filtering & sorting for the full syntax, including date-range bounds and the difference from saved-report RQL.
Unknown query parameters are ignored
A query parameter the endpoint does not declare is ignored: the request succeeds and the filter is not applied, so you get more results than you asked for. Copy parameter names from the API reference. For example, filter events by series with event_series_id (the series public_id); there is no event_series_public_id filter. Request bodies are different: an unknown body field returns 422 (see Unknown fields).
Errors¶
Requests are strict: a body field the endpoint does not accept returns 422 instead of being ignored, with a message that says how to fix the request. See Unknown fields.
All errors follow a consistent envelope:
{
"error": {
"code": "not_found",
"message": "Content item not found.",
"request_id": "req_a1b2c3d4"
}
}
Validation errors include field-level details:
{
"error": {
"code": "validation_error",
"message": "Validation failed.",
"request_id": "req_a1b2c3d4",
"field_errors": {
"email": ["A user with this email already exists."]
},
"non_field_errors": ["Cannot assign archived content."]
}
}
Error codes¶
| HTTP status | code |
Meaning |
|---|---|---|
| 400 | bad_request |
Malformed request. |
| 401 | unauthenticated / invalid_token |
Missing or invalid token. |
| 403 | forbidden |
Authenticated, but the operation is not permitted. |
| 404 | not_found |
Resource does not exist or is not visible to this token. |
| 409 | conflict / duplicate |
The operation conflicts with the current state. |
| 412 | precondition_failed |
If-Match ETag did not match the current resource. |
| 422 | validation_error |
Request body failed validation. |
| 429 | rate_limited |
Too many requests; see Rate limits. |
| 500 | internal_error |
Unexpected server error. Quote the request_id to support. |
Always log request_id with every failure — it's the fastest path to a resolution. For which failures are safe to retry and how to avoid duplicate creates, see Errors, retries & idempotency.
Rate limits¶
All endpoints are rate-limited. Specific per-tier limits are not yet published; the service returns sensible defaults during the preview.
Every response includes rate-limit headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Your rate limit (requests per window). |
X-RateLimit-Remaining |
Requests remaining in the current window. |
X-RateLimit-Reset |
Seconds until the limit resets. |
When a request is rate-limited, the response is HTTP 429 with the standard error envelope and a Retry-After header.
ETags and concurrency¶
Resources that support optimistic concurrency return an ETag header on GET and require If-Match on PATCH. A mismatched ETag returns HTTP 412 precondition_failed — re-fetch the resource and retry.
Bulk endpoints¶
Bulk endpoints (e.g. POST /content-items/bulk/archive) operate on up to 100 resources per request. Per-item failures are isolated — a partial batch returns HTTP 207 Multi-Status with a per-item result list. Some bulk flows are asynchronous and return a job to poll instead. See Bulk operations & async jobs for both patterns.