# Requests and responses

> Pages, filters, syncing, error codes, retries with idempotency keys, and rate limits.

- Canonical page: https://synconone.com/docs/api-conventions
- Section: Developers
- Updated: 2026-10-22

Every endpoint behaves the same way for pages, filters, errors, retries and limits. Learn these once.

## Responses

A success returns `{ "data": … }`: one object, or a list. Objects carry an `object` field naming what they are, a public `id` such as `pr_1a2b3c4d5e6f7a8b`, and timestamps in ISO 8601 with a zone. Every response has an `X-Request-Id` header; quote it if you contact support.

## Lists, pages and sync

Lists return `{ "data": [...], "pagination": { "nextCursor", "hasMore" } }`. Ask for up to 100 with `limit`, and pass `nextCursor` back as `cursor` for the next page. A cursor belongs to the filters it was made with; using it with others answers `422`.

To keep another system in step, ask for `sort=-updated_at` with `updated_since` set to the newest `updated_at` you last saw. Some lists cannot be paged and say so in the reference; they return the newest items and `hasMore`.

```bash
curl "https://synconone.com/api/v1/people?sort=-updated_at&updated_since=2026-11-05T00:00:00Z&limit=100" \
  -H "Authorization: Bearer sk_live_YOUR_KEY"
```

## Filters, search and custom fields

- `q` searches a record's main text. Other query parameters filter by equality; each endpoint lists its own in the reference.
- Custom fields are written and read by their key beside the built-in ones. A value that does not fit the field's type is a `422` that names the field.
- Relations take public ids: a deal's `person` is a `pr_…` id. An id for something the key cannot see is a `422`, never a silent skip.

## Errors

Every failure has the same shape. Branch on `code`, never on the wording of `message`, which is written for people and may change.

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The request body is invalid.",
    "requestId": "req_5b1f…",
    "details": [{ "field": "email", "message": "Enter a valid email address." }]
  }
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| 401 | `unauthorized` | No key, or a key that is wrong, revoked or expired. |
| 403 | `forbidden` | The key lacks the scope, or its role cannot do this. |
| 404 | `not_found` | It does not exist, or this key cannot see it. |
| 409 | `conflict` | The state forbids it, such as a ticket move it cannot make. |
| 422 | `validation_failed` | Something in the request is wrong; details say what. |
| 422 | `idempotency_key_reused` | The key was used with a different request. |
| 429 | `rate_limited` | Too many requests. Wait for Retry-After. |
| 500 | `internal` | Our side. Retry; quote the request id if it persists. |

## Retries and idempotency

Send an `Idempotency-Key` (any unique string up to 255 characters) on creates a retry could repeat. Retrying with the same key and body replays the first answer and sets `Idempotent-Replayed: true`; the same key with a different body is refused. Keys are remembered for 24 hours. `GET`, `PATCH` and `DELETE` are safe to retry as they are.

## Rate limits

Each key has a requests-per-minute limit, set when it is made. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. Past the limit the API answers `429` with `Retry-After` in seconds; wait that long, then continue. Back off rather than retrying in a tight loop. Sending to people has its own, lower limits, noted where they apply: replies in the live chat inbox, and invitations sent by meetings.
