# Conventions

> Rules that hold across every endpoint.

## Requests

- JSON bodies, `Content-Type: application/json` (uploads may use `multipart/form-data`).
- Field names are `snake_case`.
- A task's input takes exactly the fields in its schema (`GET /v1/tasks/{name}`). Any other field
  — a misspelled name, or one the server fills itself — is refused with
  `422 field_not_accepted`, and `field` names it, so a typo shows up on the first call. (This
  rule is for the task input; query parameters work as each endpoint documents them.)
  Known fields are validated — a wrong type, a value out of range or a missing required field is a
  `422` naming the `field`.

## Pagination

List endpoints use cursors.

```http
GET /v1/jobs?limit=50
```

```json
{ "items": [ … ], "has_more": true, "next_cursor": "AXsiZiI6IjQ0MTM2ZmEzNTVi…" }
```

- `limit`: page size (default and maximum per endpoint in the OpenAPI spec).
- `has_more` says whether another page follows. While it is `true`, pass `cursor=<next_cursor>`
  for the next page. On the last page `has_more` is `false` and `next_cursor` is absent.
- Cursors are opaque; do not build or parse them. They may expire — restart the listing if one is
  refused.
- Order is newest first unless an endpoint says otherwise (a job result's `files` are in the order
  the task documents — [Jobs](https://docs.refabric.com/task-apis/calling-tasks/asynchronous-jobs#result)).

```python
def all_items(session, url, **params):
    cursor = None
    while True:
        page = session.get(url, params={**params, **({"cursor": cursor} if cursor else {})}).json()
        yield from page["items"]
        if not page["has_more"]:
            return
        cursor = page["next_cursor"]
```

## Time

ISO-8601 in UTC with a `Z` suffix: `2026-09-28T10:00:00Z`. Fractional seconds may appear. Durations
in a body are integer **milliseconds**, and their field says so (`duration_ms`, `wait_ms`,
`run_ms`); a setting whose name ends in `_seconds` (`limits.*.max_seconds` of `GET /v1/meta`,
`grace_period_seconds`) says seconds the same way. Header values stay in **seconds**
(`Retry-After`, `Prefer: wait=N`), and webhook `X-Refabric-Timestamp` is Unix seconds, because it
is part of a signed message.

## Ids

All ids and references are **opaque strings** (`j_…`, `art:…`, `file:…`, `req_…`, `key_…`).
Do not parse them, assume a length, or build them. Compare them as exact strings.

## Enums

Every closed option in an input schema lists its allowed values, each with a description.

- **Inputs:** only the listed values are accepted.
- **Outputs:** new values may appear at any time (for example a new job status or error type).
  **Your code must tolerate unknown values** — treat them as "other", not as an error.

## Nulls and missing fields

- In responses, a field that has no value is present with `null`, unless the schema marks it
  optional.
- New response fields may be added at any time; ignore fields you do not know.
- In requests, omitting an optional field and sending `null` mean the same thing: use the default.

## Numbers and money

Credits are whole numbers (integers) in the account's credit unit, sent as integers. They appear
in `X-Refabric-Credits`, the estimate response, and account endpoints — never inside job results. Prices per task:
[Pricing](https://docs.refabric.com/task-apis/pricing).

## Headers

| Header | Direction | Meaning |
|---|---|---|
| `Authorization` / `x-api-key` | request | [Authentication](https://docs.refabric.com/api-reference/platform/authentication) |
| `Idempotency-Key` | request | [Idempotency](https://docs.refabric.com/api-reference/platform/idempotency) |
| `X-Request-ID` | both | your correlation id (optional) inbound; our `request_id` always outbound |
| `X-Client-Request-ID` | response | your `X-Request-ID`, echoed when it was a valid one |
| `Prefer: wait=N` | request | [sync mode](https://docs.refabric.com/task-apis/calling-tasks/synchronous) |
| `X-Refabric-Start-Timeout` | request | [start deadline](https://docs.refabric.com/task-apis/calling-tasks/asynchronous-jobs#start-deadline) |
| `Refabric-Version` | request | [dated version pin](https://docs.refabric.com/api-reference/platform/versioning) |
| `X-Refabric-Credits`, `X-Refabric-Credit-Type` | response | on the submit answer (`POST /v1/tasks/{name}`) only, and only when the task costs credits: the credits held, and their `credit_type` (the same value `/estimate` and `GET /v1/pricing` answer) |
| `X-Refabric-Error-Type` | response | the `error.type` of an error response |
| `X-Refabric-Start-Timeout-Type` | response | `user` when a job failed its start deadline |
| `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After` | response | [Rate limits](https://docs.refabric.com/task-apis/limits#rate-limits) |

Every response, success or error, carries `X-Request-ID` — always OUR id (`req_…`). If you send
`X-Request-ID` (at most 128 characters of `A-Z a-z 0-9 . _ : -`), we log it next to ours and echo it
back as `X-Client-Request-ID`; anything else is ignored, never refused.
