# Conventions

Rules every endpoint follows. Read them once before you build.

## Requests

- JSON in and out (`Content-Type: application/json`), UTF-8. The only exceptions are the token route (form-encoded) and bordereaux (CSV out).
- Unknown body fields and query parameters are rejected (`400 bad_request`), so typos fail loudly instead of being ignored.
- Bodies are limited to 100 KB.

| Header | Direction | Use |
| --- | --- | --- |
| `Authorization` | request | `Bearer <accessToken>` on every route except the token route. |
| `Idempotency-Key` | request | Safe retries of writes. See below. |
| `If-Match` | request | The version you read, to avoid overwriting a newer change. See below. |
| `X-Request-Id` | both | Your id for the call (a fresh UUID), echoed in the response and our logs. |
| `ETag` | response | Version of an editable resource: `W/"7"`. |
| `RateLimit`, `RateLimit-Policy`, `Retry-After` | response | Rate limiter state. See Rate limits. |

## Response envelope

Every JSON response, success or error, has the same four keys:

**200 OK**

```
{
  "status": "success",
  "data": { "id": "6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e", "status": "active" },
  "error": null,
  "metadata": {
    "timestamp": "2026-11-05T09:15:02.481Z",
    "path": "/api/v1/partner/policies/6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e",
    "method": "GET",
    "requestId": "8d7e2c1a-41f0-4a4e-9a55-6f0b3c2d1e77"
  }
}
```

- `status` is `success` (2xx), `fail` (4xx: fix the request) or `error` (5xx: our side, retry later).
- Read the HTTP status code and `error.code`. Never parse `error.message`: it's English text for developers and can change. See [Errors](/guides/errors.md).
- `metadata.requestId` identifies the call. Log it and quote it to support.

## Data formats

| Kind | Format | Example |
| --- | --- | --- |
| Ids | UUID strings. Path ids that aren't UUIDs get `400`. | `6f1c2d3e-4b5a-4c69-8d7e-1f2a3b4c5d6e` |
| Money | EGP, decimal **strings** with 2 decimals. Prices are tax-inclusive. | `"9500.00"`, `"-620.27"` |
| Instants | ISO 8601 in UTC. | `2026-11-05T09:15:02.481Z` |
| Calendar dates | `YYYY-MM-DD`, on the Cairo (`Africa/Cairo`) calendar. | `2026-11-05` |
| Phone numbers | Egyptian mobiles, returned as `01XXXXXXXXX`. | `01012345678` |
| Translatable text | Both languages, always. | `{ "en": "Gold Care", "ar": "جولد كير" }` |
| Enums | Lowercase snake_case, case-sensitive. | `documents_requested` |

> Parse money with a decimal type (`decimal` in C#, `Decimal` in Python, `BigDecimal` in Java, a decimal library in JavaScript). Never with a binary float: rounding errors break reconciliation.

## Pagination

Lists take `page` (from 1) and `size` (1–100, default 20) and return:

**data**

```
{
  "items": [ { "id": "…" } ],
  "meta": {
    "page": 2,
    "size": 20,
    "totalItems": 47,
    "totalPages": 3,
    "hasNextPage": true,
    "hasPreviousPage": true
  }
}
```

## Change feed

Policies, cancellations, claims and packages accept `updatedSince`. Sorted by `updatedAt` ascending, a list is a change feed: a reliable way to sync, and the safety net behind webhooks.

**Pseudocode**

```
# Every few minutes, per resource:
since = load_checkpoint("policies")          # e.g. 2026-11-04T00:00:00.000Z
loop:
  page = GET /partner/policies?updatedSince={since}&sortBy=updatedAt&sortOrder=asc&size=100
  for row in page.items:
      upsert(row) unless already_seen(row.id, row.updatedAt)
  if page.items: since = page.items[-1].updatedAt; save_checkpoint("policies", since)
  if not page.meta.hasNextPage: break
```

Rows changed in the same millisecond can appear on two pages, so make the upsert idempotent on `id` + `updatedAt`.

## Idempotency

A network failure can hide whether a write happened. Send `Idempotency-Key: <UUID v4>` on writes; if you retry with the same key, the action runs once and every retry gets the first result.

- One key per **action** (one cancellation, one approval), reused for every retry of it. A new action gets a new key.
- Successful results are kept for 24 hours.
- A failed request (validation or business error) releases the key, so you can fix the input and retry with the same key.

| Status | Code | Meaning | Do |
| --- | --- | --- | --- |
| 409 | `idempotency_request_is_still_processing` | The first request with this key is still running. | Wait `Retry-After` (1 s), retry with the same key. |
| 422 | `idempotency_key_already_used` | Same key, different URL or body. | Bug: use a new key for a new action. |
| 400 | `bad_request` | Key missing where required, or not 8–128 of `A-Z a-z 0-9 _ -`. | Send a UUID v4. |

Each endpoint shows whether it **requires** or **supports** the header. Required on money-moving actions (cancel a policy, record a refund).

## Concurrent edits

Your staff may edit the same resource in the Wathiqaty portal while your system does through the API. Editable resources return a version in `ETag` (and a `version` field). Send it back as `If-Match`: if someone changed the resource since you read it, you get `412 version_conflict` with `details.currentVersion` instead of silently overwriting their change. Reload, re-apply, resend.

## Files

Files are never streamed through the API. Responses carry a `url` to download from directly. To upload (settlement proof), ask for a presigned upload URL, send the file straight to storage, then reference its `fileKey`.

## Personal data

Policies carry the policyholder's personal data, including the national id verified by eKYC. Request `policies:read` only for systems that need it, store only what your policy administration requires, and handle it under Egypt's Personal Data Protection Law (151/2020) and the integration agreement.
