> ## Documentation Index
> Fetch the complete documentation index at: https://docs.redbark.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Syncs, runs and categories

> Manage syncs, run them, watch runs, and shape the category taxonomy

## The sync object

```json theme={null}
{
  "id": "sync_8Pq2Rs4tUv6wXy8zAb1cDe",
  "object": "sync",
  "name": "Everyday to Sheets",
  "type": "transactions",
  "enabled": true,
  "status": "idle",
  "attention": null,
  "destination": { "id": "dest_3Fg5Hj7kLm9nPq1rSt3uVw", "type": "sheets", "name": "Budget 2026", "mapping_mode": "bulk" },
  "accounts": ["acct_9Lk2Mn4pQr6sTu8vWx1yZb"],
  "account_mappings": [],
  "options": { "data_from": "2026-01-01", "include_pending": false, "mask_account_numbers": false, "custom_categories": true, "include_balances_tab": false },
  "rulesets": [],
  "latest_run": null,
  "last_synced_at": "2026-08-21T02:00:00.000Z",
  "metadata": null,
  "livemode": true,
  "created": "2026-03-01T10:05:00.000Z",
  "updated": "2026-08-21T02:00:00.000Z"
}
```

`status` is `idle`, `running` (a run is queued or in progress) or `attention` (the last run failed or the sync is parked; `attention.code` says why). `latest_run` is filled with `include[]=latest_run`.

## Create a sync

```bash theme={null}
curl https://api.redbark.com/v2/syncs \
  -H "Authorization: Bearer rbk_live_..." -H "Redbark-Version: 2026-10-01.wattle" \
  -H "Idempotency-Key: create-everyday-sheets" -H "Content-Type: application/json" \
  -d '{
    "type": "transactions",
    "name": "Everyday to Sheets",
    "destination": "dest_3Fg5Hj7kLm9nPq1rSt3uVw",
    "accounts": ["acct_9Lk2Mn4pQr6sTu8vWx1yZb"],
    "mapping_mode": "per_account",
    "options": { "data_from": "2026-01-01", "include_pending": false },
    "metadata": { "team": "finance" }
  }'
```

Returns 201 with the sync and, unless `run_now: false`, `latest_run` set to the queued first run. Rules:

* The destination must be `active` (`400 destination_incomplete` otherwise) and not already used by a sync (`409 destination_in_use`).
* YNAB needs `account_mappings` (budget account per bank account, ids from `GET /v2/destinations/{id}/resources?type=account&parent=<budget>`); Sheets `per_account` without mappings gets a tab per banking account created for you.
* Brokerage accounts need a Sheets destination; `holdings`/`trades` syncs need brokerage accounts.
* `options.data_from` is clamped to the last 84 months, and a date in the future is clamped to today; the default is 6 months (3 on trial). The response carries the clamped value, so read it back rather than assuming what you sent.
* Plan sync limit: `403 sync_limit_reached`.

## Endpoints

| Method and path                               | Scope              | Tier             | Notes                                                                                                                 |
| --------------------------------------------- | ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------- |
| `POST /v2/syncs`                              | syncs:write        | mutating         | See above                                                                                                             |
| `GET /v2/syncs`                               | syncs:read         | cheap            | Filters `status[]`, `enabled`, `destination`; include `latest_run`                                                    |
| `GET /v2/syncs/{id}`                          | syncs:read         | cheap            |                                                                                                                       |
| `POST /v2/syncs/{id}`                         | syncs:write        | mutating         | Partial update (`PATCH` accepted). Fields: `name`, `enabled`, `accounts`, `account_mappings`, `options.*`, `metadata` |
| `DELETE /v2/syncs/{id}`                       | syncs:write        | mutating         | Run history goes with it; rulesets and the destination stay                                                           |
| `POST /v2/syncs/{id}/run`                     | syncs:write        | mutating         | 202 with a `queued` sync\_run; 409 `sync_already_running`                                                             |
| `POST /v2/syncs/{id}/resync`                  | syncs:write        | mutating         | `{ "confirm": true }`; rewinds to `options.data_from` and queues a run                                                |
| `GET /v2/syncs/{id}/runs`                     | syncs:read         | cheap            | Last 90 days, filter `status[]`                                                                                       |
| `GET /v2/sync_runs/{id}`                      | syncs:read         | cheap            |                                                                                                                       |
| `POST /v2/sync_runs/{id}/cancel`              | syncs:write        | mutating         | Only while `queued`; otherwise 409 `run_not_cancellable`                                                              |
| `GET`/`POST /v2/syncs/{id}/category_mappings` | syncs:read / write | cheap / mutating | Provider category → destination category (YNAB)                                                                       |
| `POST /v2/syncs/{id}/rulesets`                | syncs:write        | mutating         | `{ rulesets: ["rset_…"] }` attaches (one per sync); `[]` detaches                                                     |

On a `per_account` Sheets sync, adding accounts needs `account_mappings` for the new ones (`400 mapping_required`); brokerage accounts share the Holdings and Trades tabs and need none.

## The sync\_run object

```json theme={null}
{
  "id": "run_4Kt7Lm2nPq9rSv1wXy3zAb",
  "object": "sync_run",
  "sync": "sync_8Pq2Rs4tUv6wXy8zAb1cDe",
  "destination": "dest_3Fg5Hj7kLm9nPq1rSt3uVw",
  "status": "succeeded",
  "trigger": "api",
  "counts": { "processed": 120, "added": 4, "updated": 1, "rejected": 0, "skipped_by_rules": 2 },
  "failure": null,
  "started_at": "2026-08-21T02:00:01.000Z",
  "completed_at": "2026-08-21T02:00:09.000Z",
  "duration_ms": 8120,
  "livemode": true,
  "created": "2026-08-21T02:00:00.000Z"
}
```

`status`: `queued` → `running` → `succeeded` | `failed` | `cancelled`. `trigger`: `schedule`, `api`, `dashboard`, `webhook`, `resync`. A failed run carries `failure.code` (`destination_auth_failed`, `destination_resource_missing`, `rate_limited`, `timeout`, `superseded`, `run_failed`) and `retryable`.

Watch a run: poll `GET /v2/sync_runs/{id}` or subscribe an event destination to `sync_run.*`.

## Categories

Your taxonomy is a tree one level deep: groups (`parent: null`) contain categories. Each category claims provider category codes (`sources`); a transaction resolves to the category that claims its subcategory, then its primary category.

```json theme={null}
{
  "id": "cat_8Mn3Pq5rSt7uVw9xYz1aBc",
  "object": "category",
  "name": "Groceries",
  "parent": "cat_2Ab4Cd6eFg8hJk0lMn2oPq",
  "icon": "🛒",
  "color": "#3a7",
  "position": 0,
  "archived": false,
  "sources": ["FOOD_AND_DRINK_GROCERIES"],
  "category_count": 0,
  "metadata": null,
  "livemode": true,
  "created": "2026-03-01T10:05:00.000Z",
  "updated": "2026-08-21T02:00:00.000Z"
}
```

| Method and path                    | Scope            | Notes                                                                                       |
| ---------------------------------- | ---------------- | ------------------------------------------------------------------------------------------- |
| `GET /v2/categories`               | categories:read  | Whole tree; `?parent=none` for groups, `?parent=cat_…` for one group, `?archived=`          |
| `POST /v2/categories`              | categories:write | `{ name, parent?, icon?, color?, metadata? }`; omit `parent` to create a group              |
| `GET /v2/categories/{id}`          | categories:read  |                                                                                             |
| `POST /v2/categories/{id}`         | categories:write | `name`, `parent` (reparent a category), `icon`, `color`, `position`, `archived`, `metadata` |
| `DELETE /v2/categories/{id}`       | categories:write | `?cascade=true` for a non-empty group; `?reassign_to=cat_…` moves its sources               |
| `POST /v2/categories/{id}/sources` | categories:write | `{ sources: [...] }` replaces the claimed set; response lists `released_from`               |
| `POST /v2/categories/reset`        | categories:write | `{ "confirm": true }` reseeds the default template                                          |
| `GET /v2/provider_categories`      | data:read        | The codes you can claim                                                                     |
