---
title: "API reference"
description: "Every operation with scope, cost, headers, body and errors, built from the frozen spec. Examples in curl, the TypeScript SDK and the Python SDK."
lang: en
url: https://developers.bodo-app.com/en/reference/
apiVersion: 2026-11-01
---

# API reference

Every operation with scope, cost, headers, body and errors, built from the frozen spec. Examples in curl, the TypeScript SDK and the Python SDK.

OpenAPI 3.1 · Version 2026-11-01 · 42 operations · curl · TypeScript · Python

## Code examples

Every example runs against the sandbox in the SDKs' CI.

**Create a contact**

curl:

```bash
curl -X POST https://api.bodo-app.com/v1/contacts \
  -H "Authorization: Bearer $BODO_API_KEY" \
  -H "Bodo-Preview: true" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"firstName":"Erika","lastName":"Mustermann","email":"erika@musterfirma.de"}'
```

TypeScript · SDK:

```typescript
// Portal D3 · Reference — the contact operations with the SDK: create, read, list, update, delete.
// Run: BODO_API_KEY=bodo_uk_test_… bun examples/reference-contacts.ts
import { Bodo, BodoApiError } from "@bodo/api";

const bodo = new Bodo({ apiKey: process.env.BODO_API_KEY, preview: true, locale: "en" });

const created = await bodo.contacts.create(
  { firstName: "Max", lastName: "Mustermann", email: "max@musterfirma.de" },
  { idempotencyKey: `quickstart-${Date.now()}` }, // optional: your own key dedupes across processes
);

const read = await bodo.contacts.get(created.id);

// Optimistic update: If-Match with the revision you read.
const updated = await bodo.contacts.update(
  read.id,
  { jobTitle: "Einkauf" },
  { ifMatch: `"r${String(read.revision)}"` },
);
console.log(updated.jobTitle);

// The iterator follows nextCursor by itself.
for await (const contact of bodo.contacts.list({
  updatedSince: "2026-11-01T00:00:00Z",
  limit: 50,
})) {
  if ("deleted" in contact) continue; // deletion markers only with includeDeleted
  console.log(contact.id, contact.displayName);
}

const { count } = await bodo.contacts.count();
console.log("contacts:", count);

await bodo.contacts.delete(updated.id);

try {
  await bodo.contacts.get(updated.id);
} catch (err) {
  if (err instanceof BodoApiError && err.code === "NOT_FOUND") {
    console.log("gone:", err.requestId);
  } else {
    throw err;
  }
}
```

Python · SDK:

```python
# The contact operations with the SDK: create, read, list, update, delete.
# Run: BODO_API_KEY=bodo_uk_test_… python examples/reference_contacts.py
import os
import time

from bodo_api import Bodo, BodoApiError

with Bodo(api_key=os.environ["BODO_API_KEY"], preview=True, locale="en") as bodo:
    created = bodo.contacts.create(
        first_name="Max",
        last_name="Mustermann",
        email="max@musterfirma.de",
        idempotency_key=f"quickstart-{int(time.time())}",  # optional: dedupes across processes
    )
    read = bodo.contacts.get(created.id)

    # Optimistic update: If-Match with the revision you read.
    updated = bodo.contacts.update(read.id, job_title="Einkauf", if_match=f'"r{int(read.revision)}"')
    print(updated.job_title)

    # The iterator follows next_cursor by itself.
    for contact in bodo.contacts.list(updated_since="2026-11-01T00:00:00Z", limit=50):
        print(contact.id)

    print("contacts:", bodo.contacts.count())

    bodo.contacts.delete(updated.id)
    try:
        bodo.contacts.get(updated.id)
    except BodoApiError as err:
        if err.code != "NOT_FOUND":
            raise
        print("gone:", err.request_id)
```

## All operations

Preview operations need `Bodo-Preview: true`, otherwise the API answers `404 OPERATION_NOT_IN_VERSION`. Preview is no basis for production. Operation names are in German, as in the spec.

| Method | Path | Operation | Channel | Scope | Cost |
| --- | --- | --- | --- | --- | --- |
| GET | /v1/me | Who am I? | Stable | — | 1 point |
| GET | /v1/rate_limit | Limit status | Stable | — | 1 point |
| GET | /v1/openapi.json | The OpenAPI spec | Stable | — | 1 point |
| GET | /v1/contacts | List contacts | Stable | contacts:read | 2 points |
| POST | /v1/contacts | Create a contact | Preview | contacts:write | 5 points |
| GET | /v1/contacts/count | Count contacts | Stable | contacts:read | 2 points |
| GET | /v1/contacts/\{id\} | Read a contact | Stable | contacts:read | 1 point |
| PATCH | /v1/contacts/\{id\} | Update a contact | Preview | contacts:write | 5 points |
| DELETE | /v1/contacts/\{id\} | Delete a contact | Preview | contacts:write | 10 points |
| GET | /v1/companies | List companies | Stable | companies:read | 2 points |
| POST | /v1/companies | Create a company | Preview | companies:write | 5 points |
| GET | /v1/companies/count | Count companies | Stable | companies:read | 2 points |
| GET | /v1/companies/\{id\} | Read a company | Stable | companies:read | 1 point |
| PATCH | /v1/companies/\{id\} | Update a company | Preview | companies:write | 5 points |
| DELETE | /v1/companies/\{id\} | Delete a company | Preview | companies:write | 10 points |
| GET | /v1/tasks | List tasks | Stable | tasks:read | 2 points |
| POST | /v1/tasks | Create a task | Preview | tasks:write | 5 points |
| GET | /v1/tasks/count | Count tasks | Stable | tasks:read | 2 points |
| GET | /v1/tasks/\{id\} | Read a task | Stable | tasks:read | 1 point |
| PATCH | /v1/tasks/\{id\} | Update a task | Preview | tasks:write | 5 points |
| DELETE | /v1/tasks/\{id\} | Delete a task | Preview | tasks:write | 10 points |
| POST | /v1/tasks/\{id\}/complete | Complete a task | Preview | tasks:write | 5 points |
| GET | /v1/documents | List documents | Stable | documents:read | 2 points |
| POST | /v1/documents | Create a document from an upload | Preview | documents:write | 10 points |
| GET | /v1/documents/count | Count documents | Stable | documents:read | 2 points |
| GET | /v1/documents/\{id\} | Read a document | Stable | documents:read | 1 point |
| PATCH | /v1/documents/\{id\} | Update a document | Preview | documents:write | 5 points |
| DELETE | /v1/documents/\{id\} | Delete a document | Preview | documents:write | 10 points |
| GET | /v1/documents/\{id\}/content | Download the file | Stable | documents:read | 1 point |
| POST | /v1/uploads | Create an upload session | Preview | documents:write | 5 points |
| POST | /v1/batch | Bundle several calls | Preview | — | 0 points |
| GET | /v1/operations/\{id\} | Poll an operation | Stable | — | 1 point |
| GET | /v1/webhook_endpoints | List webhook endpoints | Stable | webhook_endpoints:read | 2 points |
| POST | /v1/webhook_endpoints | Create a webhook endpoint | Preview | webhook_endpoints:write | 5 points |
| GET | /v1/webhook_endpoints/\{id\} | Read a webhook endpoint | Stable | webhook_endpoints:read | 1 point |
| PATCH | /v1/webhook_endpoints/\{id\} | Update a webhook endpoint | Preview | webhook_endpoints:write | 5 points |
| DELETE | /v1/webhook_endpoints/\{id\} | Delete a webhook endpoint | Preview | webhook_endpoints:write | 10 points |
| POST | /v1/webhook_endpoints/\{id\}/test | Send a test event | Preview | webhook_endpoints:write | 5 points |
| POST | /v1/webhook_endpoints/\{id\}/rotate_secret | Rotate the signing secret | Preview | webhook_endpoints:write | 5 points |
| GET | /v1/webhook_endpoints/\{id\}/deliveries | Read the delivery log | Stable | webhook_endpoints:read | 2 points |
| GET | /v1/events | List events | Stable | — | 1 point |
| GET | /v1/events/\{id\} | Read an event | Stable | — | 1 point |

## Spec and Try it

- [Download openapi.2026-11-01.json](/openapi/openapi.2026-11-01.json), byte-identical with the artifact in the repo
- With a key: `GET /v1/openapi.json`
- [Try it](/en/reference/try-it/) with your sandbox key
