# REST API (/docs/api)

The REST API serves JSON under `/v1` on the API origin. It uses the same
credentials as the MCP server: a [team API key](/docs/agents/authentication#api-keys)
or an OAuth access token.

| Setting | Value |
| --- | --- |
| Base URL | `https://api.osorik.dev/v1` |
| Reference | [`https://api.osorik.dev/v1/docs`](https://api.osorik.dev/v1/docs) |
| OpenAPI 3.1 document | [`https://api.osorik.dev/v1/openapi.json`](https://api.osorik.dev/v1/openapi.json) |

## Make a request [#make-a-request]

Send the credential as a bearer token:

```bash
curl https://api.osorik.dev/v1/team \
  -H "Authorization: Bearer $OSORIK_API_KEY"
```

The [API reference](https://api.osorik.dev/v1/docs) lists every endpoint with its request
and response schemas, and builds example requests for you. It is generated
from the OpenAPI document, so it always matches what the API serves.

Writes send a JSON body. This changes one field of a transaction and leaves
the others as they are:

```bash
curl -X PATCH https://api.osorik.dev/v1/transactions/$TRANSACTION_ID \
  -H "Authorization: Bearer $OSORIK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "note": "Team offsite lunch" }'
```

## What the API covers [#what-the-api-covers]

| Area | Read | Write |
| --- | --- | --- |
| Team | Profile and your role | None |
| Contacts | List, search, get | Create, update |
| Categories | List | Create |
| Bank accounts | List | None |
| Transactions | List, filter, get | Category, note, contact, project, archive |
| Invoices | List, get, invoice settings | Create, update and delete drafts |
| Expenses | List, filter, get | Update details, archive |
| Projects | List, get | Create, update, change status |
| Time | Entries, tasks, running timer | Log, edit and delete entries; add tasks; start and stop the timer |

Issuing, voiding and sending invoices, deleting contacts or expenses,
uploading documents, connecting banks and managing members stay in the app.
Your role applies: an endpoint that needs an owner or admin returns `403` for
members.

## Conventions [#conventions]

- **IDs** are strings. An ID that doesn't exist in your team returns `404`.
- **Amounts** are decimal strings in the currency's major unit, such as
  `"1250.00"`, next to an ISO 4217 `currency` code.
- **Dates** are `YYYY-MM-DD`; timestamps are ISO 8601 in UTC.
- **Absent values** are `null`, never missing.
- **`PATCH`** changes only the fields you send. Send `null` to clear an
  optional field.
- **Creates** return `201` with the new record; deletes return
  `{ "id": "…", "deleted": true }`.

## Pagination [#pagination]

List endpoints take `limit` (1 to 100, default 25) and `cursor`, and return a
page:

```json
{
  "data": [{ "id": "…" }],
  "nextCursor": "eyJ…"
}
```

Pass `nextCursor` as `cursor` to get the next page; it is `null` on the last
page. A page can hold fewer than `limit` items before the last one, so keep
going until `nextCursor` is `null`.

## Errors [#errors]

Every error returns a JSON body with a stable `code` and a readable `message`:

```json
{
  "code": "API_KEY_INVALID",
  "message": "The API key is invalid or revoked."
}
```

| Status | Code | Meaning |
| --- | --- | --- |
| `400` | `INVALID_REQUEST` | The request failed validation |
| `400` | `INVALID_CURSOR` | The pagination cursor is invalid or expired |
| `400` | Other codes | A business rule rejected the input, such as `INVALID_DATE_RANGE` |
| `401` | `API_KEY_REQUIRED` | No bearer credential was sent |
| `401` | `API_KEY_INVALID` | The API key is invalid or revoked |
| `401` | `OAUTH_TOKEN_INVALID` | The access token is invalid or expired |
| `403` | `INSUFFICIENT_SCOPE` | The credential lacks the `read` or `write` scope the endpoint needs |
| `403` | `*_FORBIDDEN` | Your role can't do this, such as `PROJECT_MANAGEMENT_FORBIDDEN` |
| `404` | `NOT_FOUND` | No such endpoint |
| `404` | `*_NOT_FOUND` | The record doesn't exist in your team, such as `INVOICE_NOT_FOUND` |
| `409` | Conflict codes | The record's state blocks the change, such as `INVOICE_IMMUTABLE` for an issued invoice |
| `429` | `RATE_LIMITED` | Too many requests; wait for the seconds in `Retry-After` |
| `500` | `INTERNAL_ERROR` | Something went wrong on our side |

## Discovery [#discovery]

`GET https://api.osorik.dev/` returns an index that links the API, its OpenAPI document,
the reference, and the MCP endpoint.

The site publishes an [API catalog](https://www.rfc-editor.org/rfc/rfc9727) at
[`https://osorik.dev/.well-known/api-catalog`](https://osorik.dev/.well-known/api-catalog). It
links the REST API's OpenAPI document and guides, and the MCP server's
[server card](https://osorik.dev/.well-known/mcp/server-card.json), guide, and OAuth
protected resource metadata.
