Best Practices

Pagination

  • Use page and per_page on list endpoints.
  • Read meta.pagination.total and meta.pagination.total_pages instead of assuming a fixed inventory size.
  • Keep per_page at a practical value such as 20, 50, or 100.

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 ok and error.code, not only on HTTP status.
  • Log the route, HTTP status, and machine-readable error code in your integration.
  • Do not assume that every 404 means the resource never existed; inaccessible resources are intentionally hidden behind the same not-found response.
  • Treat 403 api_access_required as a billing or entitlement issue for the current account, not as a token parsing problem.
  • Treat 503 api_controls_unavailable as a temporary service issue and retry with backoff.

Retry posture

  • Safe read requests can be retried after network failures and 5xx responses with exponential backoff.
  • Do not retry 401, 403, or 404 blindly 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, or failed.
  • Treat download redirects as transport hints, not as durable resource identifiers.
  • Honor Retry-After on 429 rate_limit_exceeded and 429 quota_exceeded.
  • Use the X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers 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_conflict as 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:write for start requests and reports:read for 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 v1 routes.

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/minute and 4,000/day per account.
  • Minute-based rate limits are counted per account in a rolling 60 second 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-After value 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.