Best Practices
Pagination
- Use
pageandper_pageon list endpoints. - Read
meta.pagination.totalandmeta.pagination.total_pagesinstead of assuming a fixed inventory size. - Keep
per_pageat a practical value such as20,50, or100.
Filtering and sorting
- Use only the documented enum values for
sort,direction, and endpoint-specific filters. - Invalid query values return
400 invalid_query_parameter. - Treat undocumented query keys as unsupported until they appear in the OpenAPI contract.
Error handling
- Always branch on
okanderror.code, not only on HTTP status. - Log the route, HTTP status, and machine-readable error code in your integration.
- Do not assume that every
404means the resource never existed; inaccessible resources are intentionally hidden behind the same not-found response. - Treat
403 api_access_requiredas a billing or entitlement issue for the current account, not as a token parsing problem. - Treat
503 api_controls_unavailableas a temporary service issue and retry with backoff.
Retry posture
- Safe read requests can be retried after network failures and
5xxresponses with exponential backoff. - Do not retry
401,403, or404blindly without operator action. - Respect job and report status transitions instead of polling at sub-second intervals.
- For async job polling, use exponential backoff and stop once the status becomes
completed,blocked, orfailed. - Treat download redirects as transport hints, not as durable resource identifiers.
- Honor
Retry-Afteron429 rate_limit_exceededand429 quota_exceeded. - Use the
X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Resetheaders on authenticated responses to avoid unnecessary retries.
Idempotency note
POST /api/v1/projects, PATCH /api/v1/projects/{project_id}, POST /api/v1/projects/{project_id}/archive, and POST /api/v1/projects/{project_id}/restore require an Idempotency-Key header. POST /api/v1/projects/{project_id}/generation-jobs and POST /api/v1/reports/{report_id}/export-jobs also require an Idempotency-Key header.
- Reuse the same key only when you are replaying the exact same normalized request.
- Treat
409 idempotency_conflictas a signal that the payload changed under the same key. - Keep separate keys for create, patch, archive, restore, generation start, and export start operations.
Async workflow note
- Generation is
project -> generation job -> report. - Export is
report -> export job -> download. - Use
reports:writefor start requests andreports:readfor polling or download. - Do not expect a completed export download URL to be a guest link or a raw storage key.
Project lifecycle note
- Use archive and restore for project shell lifecycle changes.
- Archive is soft and non-destructive; it does not delete report history, shares, or stored project settings.
- Restore brings the project back to active status but does not re-enable recurring schedules automatically.
Versioning posture
- Current versioning is path-based under
/api/v1/. - Current release posture is
v1-beta. - Treat undocumented fields as unstable and avoid strict coupling to field ordering.
- Read Versioning and Lifecycle before automating against newly added fields or planning long-lived integrations.
- If a future change cannot stay backward-compatible, expect a new path version rather than a silent mutation of documented
v1routes.
Rate limits and quotas
The published contract includes 429 rate_limit_exceeded and 429 quota_exceeded, along with the headers needed to understand when to retry.
- All authenticated API operations share one account-wide bucket.
- Current authenticated API limits:
120/minuteand4,000/dayper account. - Minute-based rate limits are counted per account in a rolling
60second window. - Daily quotas are counted per account and reset at
00:00 UTC. - Authenticated requests that reach the route still consume budget even if the business result is
404,409, or another application-level error, so avoid blind polling, repeated lookups of unknown ids, and noisy retries after validation failures. - Use the documented
Retry-Aftervalue for retries instead of guessing. - Read the live response headers on every authenticated request instead of assuming that another request will see the same current window state.