# Pagination, search & responses

## Response shape

| Call | Status | Body |
|---|---|---|
| List (`GET /leads`) | `200` | An object `{ "data": [...], "count": N }` |
| Single record (`GET /leads/{id}`) | `200` | The record itself |
| Create (`POST`) | `201` | The created record, including its `id` |
| Update (`PUT` / `PATCH`) | `200` | The updated record |
| Delete (`DELETE`) | `200` | A confirmation, e.g. `{ "success": true, "message": "..." }` |

On paginated lists, `count` is the total number of records that match the query, not just the ones on the current page. On short, unpaginated lists (for example borrowers in a file), `count` equals the array length.

## Pagination

Large lists are paginated with two parameters:

| Parameter | Default | Maximum | Description |
|---|---|---|---|
| `limit` | `50` | `500` | Records per page |
| `offset` | `0` | `1000000` | Records to skip |

An invalid value (negative or non-numeric) does not return an error - it falls back to the default. A value above the maximum is capped. Results are sorted newest first (`created_at` descending) unless an endpoint says otherwise.

Walking through every record (a record created while you page shifts later pages by one, so the sample de-duplicates by `id`):

```js
// bantaFetch handles 429 - see Rate limits
async function fetchAll(path, params = {}) {
  const limit = 500
  const byId = new Map()
  for (let offset = 0; ; offset += limit) {
    const url = new URL(`https://app.banta.co.il/api-service${path}`)
    for (const [k, v] of Object.entries({ ...params, limit, offset })) url.searchParams.set(k, v)
    const res = await bantaFetch(url, { headers: { 'x-api-key': process.env.BANTA_API_KEY } })
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    const { data, count } = await res.json()
    for (const row of data) byId.set(row.id, row)
    if (data.length < limit || offset + limit >= count) return [...byId.values()]
  }
}

// fetchAll('/leads', { status: 'new' })
```

> When paging through a lot of data, mind the [rate limit](https://docs.banta.co.il/guides/rate-limits.md) of 60 requests per minute. Pages of 500 are the most efficient way through.

## Search

Endpoints that support search accept a `search` parameter. Search is case-insensitive and matches the term inside several fields at once (for example name, phone and email). The exact fields are listed on each endpoint.

```bash
curl "https://app.banta.co.il/api-service/customers?search=Israeli" -H "x-api-key: $BANTA_API_KEY"
```

The characters `, ( ) % _ \ " ' *` are removed from the term, the term is cut to 100 characters, and the rest is matched literally as a substring. In practice:

- `0501234567` won't find `050-1234567`. Search a part of the number without a dash, such as `1234567`.
- A name with a geresh, such as `ג'ורג'`, won't match. Search a part without the punctuation.
- An email that contains `_` won't match as a whole. Search its domain or another part.

## Filtering

Some lists accept extra filter parameters, for example `status` on the leads list. Available parameters are listed on each endpoint.

## PUT vs PATCH

On resources that support both, `PUT` and `PATCH` behave the same: a **partial update**. Send only the fields you want to change; fields you don't send are left as they are. Fields that cannot be updated (such as `id`, `organization_id` and `created_at`) are silently ignored. If no updatable field is sent, the API returns `400 No updatable fields provided`.

IDs in the path: a malformed ID returns `400`; an ID that doesn't exist, was deleted or belongs to another organization returns `404`.

## Side effects of writes

A write through the API behaves like the same change in the app: it can run the organization's automations (which may email or WhatsApp borrowers), appear in timelines and notify the team. Each endpoint lists its side effects.

- Don't re-send unchanged records on every sync. `PUT`/`PATCH` on a mortgage file writes only the fields that actually changed and does nothing when nothing changed, and sending a lead's or a file's current status writes nothing - but other resources may still record a change.
- Changes made through the API are not pushed to the Google or Microsoft address book that the organization syncs with banta, and the API does not create Google Drive folders.

## User IDs

`assigned_to`, `watcher_id` and `recipient_user_id` take the ID of an active user in your organization. The API doesn't list users yet; read IDs from existing records (`assigned_to` on a lead or mortgage file, `assignees` on a task, `watcher_id` in `GET /mortgage-files/{id}/watchers`).

An ID that isn't an active user in the organization returns `400` on mortgage files and reminders, and `404` for `watcher_id`. On leads and tasks it is ignored: a new record is created unassigned, and an update keeps the current assignee.

## Converting a lead to a mortgage file

There is no conversion endpoint yet. To turn a lead into a mortgage file:

1. Create the file with `POST /mortgage-files`, sending `source_lead_id` (the lead's ID) and the `borrowers`.
2. Change the lead's status with `PATCH /leads/{id}/status` to the status that marks a converted lead in your organization (for example `converted`; see `GET /leads/statuses`).

Unlike conversion in the app, documents, notes and calculator mixes of the lead are not copied to the new file.

## Fixed values

Enum values (employment types, mortgage types, statuses and so on) are available from the endpoints under [System lists](https://docs.banta.co.il/#tag/lists). Use them to build forms instead of hard-coding values. Lead and file statuses can be customized per organization, so read them from `/leads/statuses` and `/mortgage-files/statuses`.

---

Part of the banta API documentation. Full index: https://docs.banta.co.il/llms.txt
