# Using DayZero from an AI Agent

> How coding agents and LLM tools should discover endpoints, authenticate, read the docs programmatically, and call the REST API safely.

- **Category:** Getting Started
- HTML version: https://www.ondayzero.com/docs/guides/ai-agents
- All guides: https://www.ondayzero.com/docs/guides/index.md
- Endpoint catalog: https://www.ondayzero.com/docs/reference/index.md

---

# Using DayZero from an AI Agent

This guide is written for the agent, not the human. If you are an LLM-based tool that has been handed a DayZero API token and a task, read this page first — it tells you where every other piece of documentation is, how to find the one endpoint you need without reading all 1,100+, and which conventions will otherwise cost you a round trip.

If you are a person configuring an assistant like Claude, ChatGPT, Cursor or Codex to *talk* to DayZero, you probably want the [MCP Server](/docs/guides/mcp) guide instead — that path uses OAuth in the browser and needs no API token.

## Two ways in

| You are… | Use | Auth |
|---|---|---|
| An assistant answering questions or taking actions on a user's behalf in chat | **MCP server** at `https://api.ondayzero.com/mcp` — [guide](/docs/guides/mcp) | OAuth 2.1 sign-in in the browser; `dz_…` tokens do **not** work here |
| A coding agent, script, workflow or backend calling DayZero directly | **REST API** at `https://api.ondayzero.com/api/v1/…` — this guide | `Authorization: Bearer dz_…` + `x-business-id` |

The rest of this page is about the REST path.

## Where the documentation is

Everything is plain text or JSON, stable-URL, cacheable, and linked from one place. Read them in this order and stop as soon as you have what you need:

| Step | URL | What you get |
|---|---|---|
| 1 | `https://www.ondayzero.com/llms.txt` | Short index of every documentation surface (this table, essentially) |
| 2 | `https://www.ondayzero.com/docs/search?q=void+invoice` | JSON: ranked endpoints and guides for a task, each with its `markdown_url` and `openapi_url` |
| 3 | `https://www.ondayzero.com/docs/reference/{tag}/{operation}.md` | One endpoint: auth, parameters, request/response fields, an **example request that validates**, curl / Python / JS samples |
| 4 | `https://www.ondayzero.com/docs/reference/{tag}/{operation}/openapi.json` | The same endpoint as a self-contained OpenAPI 3.1 document (only the schemas it uses) |

Broader views when you need them:

- **Endpoint catalog** — `https://www.ondayzero.com/docs/reference/index.md`: one line per endpoint (`METHOD /path — summary → tag/operation`), grouped by tag, ~160 KB for the whole API. Read it once if you'd rather scan than search.
- **Per-tag bundles** — `https://www.ondayzero.com/docs/reference/{tag}.md` and `…/{tag}/openapi.json`: everything about, say, `invoices` in one document.
- **Guides** — `https://www.ondayzero.com/docs/guides/index.md`, and any guide as `…/docs/guides/{slug}.md`.
- **Full public spec** — `https://api.ondayzero.com/api/v1/openapi.json`. Large; prefer the slices above.

Every human `/docs/**` URL also answers `Accept: text/markdown` with a `303` to its markdown twin, so you can follow links from HTML pages without rewriting URLs:

```bash
curl -sL -H "Accept: text/markdown" https://www.ondayzero.com/docs/reference/invoices/create-invoice
```

All documentation responses carry an `ETag`; send `If-None-Match` on re-reads and you'll get `304`s.

### Example: search, then read one endpoint

```bash
curl -s "https://www.ondayzero.com/docs/search?q=void+invoice&type=endpoint&limit=3"
```

```json
{
  "query": "void invoice",
  "total": 3,
  "results": [
    {
      "type": "endpoint",
      "title": "Void invoice",
      "method": "PUT",
      "path": "/api/v1/invoices/{invoice_id}/void",
      "tag": "invoices",
      "slug": "invoices/void-invoice",
      "url": "https://www.ondayzero.com/docs/reference/invoices/void-invoice",
      "markdown_url": "https://www.ondayzero.com/docs/reference/invoices/void-invoice.md",
      "openapi_url": "https://www.ondayzero.com/docs/reference/invoices/void-invoice/openapi.json",
      "score": 8.0
    }
  ]
}
```

Then fetch `markdown_url`. Optional query parameters: `type=endpoint|guide`, `limit=1..50`. A missing or empty `q` returns a `400` with usage hints rather than a `422`.

## Authenticate

1. **A person creates the token.** Tokens (`dz_…`) come from **Settings → Developers** in the DayZero dashboard. There is no endpoint that mints a first token; if you don't have one, ask the human.
2. **Find the business.** A token can usually reach more than one business, and almost every endpoint needs to know which:

   ```bash
   curl -s "https://api.ondayzero.com/api/v1/businesses" \
     -H "Authorization: Bearer dz_your_token" \
     -H "Accept: application/json"
   ```

   `GET /api/v1/businesses` is the one common endpoint that does **not** take `x-business-id`.
3. **Send both headers on everything else:**

   ```bash
   curl -s "https://api.ondayzero.com/api/v1/invoices?limit=20" \
     -H "Authorization: Bearer dz_your_token" \
     -H "x-business-id: 01912345-abcd-7000-8000-000000000001" \
     -H "Accept: application/json"
   ```

If `x-business-id` is missing you get `401` with `code: "AUTH_008"`; if the token can't access that business, `401` with `code: "AUTH_010"`. Neither is worth retrying — fix the header or ask the human.

Always send `Accept: application/json`. Errors are only ever rendered as HTML for browser-style `text/html` requests, but stating JSON removes the ambiguity.

## Conventions that cost a round trip if you guess

- **Money is integer cents.** `15000` is $150.00. Never send `150.00`.
- **IDs are UUID v7 strings.** Dates are `YYYY-MM-DD`; timestamps are RFC 3339 in UTC.
- **Responses are wrapped:** most successful responses look like `{"success": true, "data": …}`; mutations may add `message` and `code`. Lists put items and paging inside `data`.
- **Lists are cursor-paginated.** `data` holds `items`, `next_cursor`, `prev_cursor`, `has_next`, `has_prev` (and usually `limit`/`total`). Pass `limit` (endpoints default to a smaller page; max 1000) and `cursor` copied from the previous page's `next_cursor`; stop when `has_next` is `false`. There is no offset paging. Details: [Pagination](/docs/guides/pagination).
- **Deletes are soft.** Deleted records vanish from lists but their IDs stay valid, so a `GET` by ID after a delete is not proof the delete failed.
- **Operation IDs are readable** in the public spec (`create_invoice`, `list_invoices`, `void_invoice`); when two tags share a handler name the tag is appended (`list_contacts_customers`). The original FastAPI ID is preserved under `x-fastapi-operation-id` on each operation if you need it.
- **Examples are real.** The `Example request` in every endpoint's `.md`, the code samples, and the `requestBody` examples in the OpenAPI slices are validated against the request schemas in CI. Copy them and change values; don't invent a shape.

## Errors

Every non-2xx response is one envelope (schema `ErrorResponse` in the spec):

```json
{
  "error": "not_found",
  "message": "Invoice '0191…' not found",
  "code": "NOT_FOUND_006",
  "request_id": "3f0e7c2a-…"
}
```

- Branch on `code` (stable, `CATEGORY_NNN`), not on `message`.
- `400`/`422` validation errors add `errors`, a map of field name → message. Fix the named fields and resend once; if the same field fails again, re-read the endpoint's `.md` rather than guessing a third time.
- `429` is `error: "rate_limited"`, `code: "GEN_005"`. Back off exponentially (start at 1–2 s) and retry the *same* request.
- `5xx`: retry idempotent requests (`GET`, `PUT`, `DELETE`) with backoff. For a `POST` that creates something, **check first** whether it already exists (list with the filter you'd have used, e.g. `GET /api/v1/invoices?customer_id=…&status=draft`) before re-issuing — there is no idempotency-key header today, so a blind retry can double-create.
- Include `request_id` when reporting a failure to the human; it's what support looks up.

More: [Error Handling](/docs/guides/error-handling).

## Operate safely

You are usually acting on someone's books. Follow these unless the human has explicitly told you otherwise:

1. **Read before you write.** `GET` the record you're about to change and confirm it is the one the task describes (name, number, amount, status), not just the first search hit.
2. **Treat these as irreversible and confirm with the human first:** anything under `/void`, `/finalize`, `/post`, `/approve`, `/dispose`, `/merge`, bulk endpoints (`/bulk`, `/batch`, `…-all`), and `DELETE`. Say what you are about to do, with the record's identifier and amount, and wait.
3. **Prefer the narrowest endpoint.** Use `PATCH` for one field over `PUT` of the whole record; use a filtered list over paging through everything; keep `limit` small unless you need the full set.
4. **Don't loop on `4xx`.** A `401`/`403`/`404`/`422` means your request is wrong or you lack access; retrying the identical request will not change that.
5. **Never log or echo the token.** Refer to it as "the API token"; the `dz_` prefix is enough to identify it.
6. **Stay inside `x-business-id`.** If a task mentions a different business, switch the header deliberately and say so; don't mix records from two businesses in one operation.

## Worked example: void an invoice

Task: *"Void invoice INV-1042 for Acme Corp."*

```bash
# 1. Find the endpoint (once; cache the .md)
curl -s "https://www.ondayzero.com/docs/search?q=void+invoice&type=endpoint&limit=1"
curl -s "https://www.ondayzero.com/docs/reference/invoices/void-invoice.md"

# 2. Identify the record — filter, don't page
curl -s "https://api.ondayzero.com/api/v1/invoices?number=INV-1042&limit=5" \
  -H "Authorization: Bearer $DZ_TOKEN" \
  -H "x-business-id: $BUSINESS_ID" -H "Accept: application/json"

# 3. Confirm with the human: "Void INV-1042 (Acme Corp, $1,250.00, status sent)? It cannot be undone."

# 4. Act
curl -s -X PUT "https://api.ondayzero.com/api/v1/invoices/$INVOICE_ID/void" \
  -H "Authorization: Bearer $DZ_TOKEN" \
  -H "x-business-id: $BUSINESS_ID" -H "Accept: application/json"

# 5. Verify
curl -s "https://api.ondayzero.com/api/v1/invoices/$INVOICE_ID" \
  -H "Authorization: Bearer $DZ_TOKEN" \
  -H "x-business-id: $BUSINESS_ID" -H "Accept: application/json"
```

Report back with the invoice number, the new status from step 5, and the `request_id` from any error you hit along the way.

## Quick reference

| Need | URL |
|---|---|
| Index of everything | `https://www.ondayzero.com/llms.txt` |
| Find an endpoint | `https://www.ondayzero.com/docs/search?q=…` |
| One endpoint, markdown | `https://www.ondayzero.com/docs/reference/{tag}/{operation}.md` |
| One endpoint, OpenAPI | `https://www.ondayzero.com/docs/reference/{tag}/{operation}/openapi.json` |
| All endpoints, one line each | `https://www.ondayzero.com/docs/reference/index.md` |
| One tag, markdown / OpenAPI | `https://www.ondayzero.com/docs/reference/{tag}.md` · `…/{tag}/openapi.json` |
| Guides as markdown | `https://www.ondayzero.com/docs/guides/index.md` |
| Full public spec | `https://api.ondayzero.com/api/v1/openapi.json` |
| Your businesses | `GET https://api.ondayzero.com/api/v1/businesses` |
| Chat-style access instead | [MCP Server](/docs/guides/mcp) |
