Couplr Couplr developers
API v1 · explained by Flip

Errors, limits and retries

The error format and every error code, plus pagination, rate limits, retries, idempotency and versioning for the v1 API.

View Markdown

Errors#

Every error from /api/v1 is a JSON object in the RFC 9457 problem details shape, with three additions: a stable code, a request_id, and message (the same text as detail, kept for older clients).

{
  "type": "about:blank",
  "title": "Unprocessable Content",
  "status": 422,
  "detail": "The name field is required.",
  "code": "validation_failed",
  "request_id": "6f1c1f0e-5b1d-4c35-9a59-2f1a0b7c9c11",
  "message": "The name field is required.",
  "errors": {
    "name": ["The name field is required."]
  }
}
Member Meaning
type Always about:blank. Do not branch on it.
title The HTTP status phrase.
status The HTTP status code.
detail What went wrong, in a sentence. The wording may change.
code Stable and machine-readable. Branch on this.
request_id Unique per request. Also sent in the X-Request-Id response header. Give it to support.
message Same text as detail.
errors On 422 only: each invalid field mapped to its messages.

Error bodies are served as application/json. Send Accept: application/problem+json to receive that media type instead. Successful responses also carry X-Request-Id.

Error codes#

Status Code Meaning Retry?
401 api_key_missing No key was sent. No. Send a key.
401 api_key_invalid The key is unknown or revoked. No.
401 api_key_expired The key's end date has passed. No. Use a current key.
401 sandbox_expired The key belongs to a private trial sandbox whose access window has ended. No.
403 ability_missing The key lacks the ability for this endpoint. No. Use a key with the ability.
403 no_acting_admin The company has no admin user for the key to act as. No.
403 forbidden The request is not allowed for this account. No.
404 not_found No such record in this company, or no such endpoint. No.
405 method_not_allowed The endpoint does not support this HTTP method. No.
409 conflict Creating a customer with ignore_duplicates: false found a likely duplicate. The body lists candidates under duplicates. No. Resolve the duplicate.
422 validation_failed A parameter or field is invalid. See errors. No. Fix the request.
429 rate_limited Too many requests. Yes, after Retry-After seconds.
500 and above internal_error Something failed on our side. Yes, with backoff.

New codes may be added. Treat an unknown code by its HTTP status.

Pagination#

List endpoints take two query parameters:

Parameter Default Notes
page 1 Starts at 1.
per_page 25 Values above 100 are served as 100.

Each list response includes meta:

{"page": 1, "per_page": 25, "total": 61, "last_page": 3}

Request pages until page equals last_page.

To read only what changed, add updated_since with an ISO 8601 time, for example ?updated_since=2026-11-14T15:00:00Z. Every paginated list accepts it and returns records changed at or after that time. Keep the time of your last successful read and send it on the next one. Keep data in sync walks through it. Lists are ordered newest first, with the id as the final sort, so a page never repeats or skips a record unless records are created or deleted while you are paging. GET /api/v1/job-types is not paginated.

A filter with an invalid value, such as a date that is not a date, returns 422.

Rate limits#

  • 120 requests a minute for each API key.
  • 60 requests a minute per IP address for requests with a missing or invalid key.

Every response includes X-RateLimit-Limit and X-RateLimit-Remaining. When the limit is reached the response is 429 with code rate_limited and a Retry-After header giving the seconds to wait.

Requests must finish within about 28 seconds.

Retries#

  • GET requests are always safe to retry.
  • Retry 429 after the Retry-After time. Retry 5xx and network failures with exponential backoff and some random jitter, for example 1, 2, 4 and 8 seconds, then stop.
  • Do not retry other 4xx responses. The same request will fail the same way.
  • When retrying POST or PATCH, send the same Idempotency-Key so the operation cannot run twice.

Idempotency#

POST and PATCH endpoints accept an Idempotency-Key header: any string up to 100 characters that is unique to one operation, such as a UUID or your own order id.

curl -X POST "https://couplrfsm.com/api/v1/customers" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_UNIQUE_KEY" \
  -d '{"name": "Dana Whitfield", "email": "dana@example.com", "phone": "(555) 201-8890"}'
  • A repeat of the same key, by the same API key, on the same endpoint within 7 days returns the first response again without running the operation. The repeat carries the header Idempotent-Replayed: true.
  • Responses with status 500 and above are not remembered, so they can be retried with the same key.
  • The key is not compared with the request body. Reusing a key with a different body returns the first response. Use a new key for each new operation.
  • Without the header, a request runs every time it is sent.

Separately, POST /api/v1/jobs returns the existing job instead of creating a second one when an identical job (same customer, job type and day) was created in the last 30 seconds.

Versioning#

  • The version is in the path: /api/v1.
  • v1 changes only by addition: new endpoints, new optional request fields, new response fields, new error codes. Clients must ignore response fields they do not recognize.
  • A breaking change would be released under a new path prefix, with v1 kept running alongside it. No newer version exists today.

Not supported#

  • Sorting parameters. Lists have one fixed order.
  • Conditional requests (ETag, If-Match). Two updates to the same customer apply in the order they arrive.
  • Bulk or batch endpoints.

Copy this page as Markdown

Your browser blocked automatic copying. The complete page is selected below. Press Ctrl+C (Cmd+C on a Mac) to copy it.