Back to documentation

Pagination, filtering, errors and rate limits

How lists page, how requests are refused, what each status code means, and what to do when you are throttled.

Last revised: 2026-08-23

Pagination

Every list endpoint takes page and page_size and returns the same envelope. total is the count of everything matching the filters, not the size of this page - so Math.ceil(total / page_size) is the number of pages.

ParameterDefaultRange
page11 and up
page_size251–200 (larger values are clamped, not rejected)
bash·One page
curl -sS "https://your-host/api/v1/crm/contacts?page=2&page_size=100" \
  -H "Authorization: Bearer $CLIENTO_TOKEN"
json·The list envelope
{
  "items": [ { "id": 4182, "display_name": "Sara Ahmadi", "…": "…" } ],
  "page": 2,
  "page_size": 100,
  "total": 3417
}

Filtering and search

q is free-text search across the fields a person would search - names, email, company. Everything else is an explicit query parameter, and each collection documents its own set in the OpenAPI document.

bash·Search and filter
# Free-text
curl -sS "https://your-host/api/v1/crm/contacts?q=ahmadi" \
  -H "Authorization: Bearer $CLIENTO_TOKEN"

# Typed filters (tasks)
curl -sS "https://your-host/api/v1/crm/tasks?status=open&priority=high&overdue=true" \
  -H "Authorization: Bearer $CLIENTO_TOKEN"

An unknown query parameter is ignored rather than rejected - so a typo returns unfiltered results rather than an error. Check the parameter names against the OpenAPI document when a filter appears to do nothing.

The error envelope

Every refusal, from a malformed body to an internal failure, returns the same three fields. Nothing returns an HTML error page, so a client never has to guess whether a body is parseable.

json·The shape
{
  "error": "validation_error",
  "message": "Invalid payload",
  "details": { "email": ["Not a valid email address."] }
}
  • `error` - a stable machine code. Branch on this, and localize on this.
  • `message` - English diagnostic text for a developer. Do not show it to an end user and do not match on it; the wording may change.
  • `details` - structure that varies by error: field errors for validation, required for a permission refusal, reason for a business rule, retry_after for a throttle. May be null.

Status codes

CodeerrorMeaning and what to do
200-Read or update succeeded.
201-Created. The body is the new record.
204-Deleted. No body.
400validation_error or a rule codeThe payload is wrong. details names the fields; a broken business rule (for example payment_amount_not_positive) has its own code. Do not retry unchanged.
401unauthorizedMissing, expired or revoked credential. Refresh once, then stop.
403forbiddenAuthenticated but not permitted. details.required names the codes.
404not_foundNo such record - or one you may not see. The two are deliberately the same answer.
409conflict or a rule codeA uniqueness or state rule. Finance, sales, CRM and ticket rules carry their own code (for example invoice_has_payments, payment_exceeds_due) with the numbers in details; elsewhere details.reason says which. Branch on the status if you do not recognise the code.
429rate_limitedThrottled. Wait for Retry-After, then retry.
5xxinternal_errorA fault on our side. Retry with backoff; the request may or may not have applied.

Rate limits

Sensitive endpoints - sign-in, the customer portal's one-time codes, public form submissions, public invoice pages and payment callbacks - are throttled per caller. Ordinary authenticated CRUD is not throttled by default, but a workspace's operator may configure limits, so a well-behaved client handles 429 regardless.

http·A throttled response
HTTP/1.1 429 Too Many Requests
Retry-After: 42
Content-Type: application/json

{
  "error": "rate_limited",
  "message": "Too many requests",
  "details": { "retry_after": 42 }
}
  • Honour Retry-After (seconds). It is the server's own answer - a shorter wait is simply refused again.
  • Back off exponentially with jitter on 5xx. A fixed retry interval turns a brief outage into a stampede at recovery.
  • Retry GET and DELETE freely - they are idempotent. For POST, see the next section.

Idempotency

There is no global Idempotency-Key header. Where a duplicate would be harmful, the endpoint carries its own idempotency identity in the body, and it is required rather than optional - the client owns the identity, because only the client knows that a retry is a retry.

EndpointIdentityBehaviour on repeat
POST /chat/conversations/{id}/messagesclient_message_idReturns the existing message with 200 instead of 201
POST /chat/conversations/supportThe callerResolves to the same conversation
Webhook delivery (inbound to you)delivery_idSee the webhooks guide - deliveries are at-least-once

For endpoints without an idempotency field, a POST that times out has an unknown outcome. Do not blind-retry: read back with a GET filtered on something you control - an external reference you stored on the record - and create only if it is genuinely absent.

Need technical help?

If your question is not answered here, get in touch with our team.

Contact us
Pagination, filtering, errors and rate limits · Cliento Pro