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

# Response shape

> The shapes every Dairo response uses: a single object, a list under data, one-time secrets on create, and predictable delete responses.

Every Dairo response comes back in one of a few predictable shapes. Learn them once and
you can read an endpoint you have never called before, with no surprise wrapper keys to
special-case.

## A single object

When you fetch one thing, you get that thing back with an `object` field naming its
type:

```json theme={null}
{
  "object": "message",
  "id": "msg_3aR8s0Kd",
  "inboxId": "inbox_123",
  "channel": "email",
  "direction": "outbound",
  "status": "sent",
  "from": { "address": "agent@yourdomain.com", "name": "Support" },
  "to": ["dev@example.com"],
  "subject": "Welcome",
  "createdAt": "2026-06-12T10:30:00Z"
}
```

The `object` field is always present, and it always names the type: `message`, `thread`,
`domain`, `inbox`, `webhook`, `api_key`, `template`, `audience`, `audience_member`,
`contact`, `contact_handle`, `agent`, `event`, and so on. Branch on `object` whenever you
handle a value whose type you do not already know.

The type name is channel-neutral. An inbound reply, an outbound send, and an internal
agent-to-agent hop are all `object: "message"`; the [`channel`](/concepts/channels) field
says how each one traveled.

## A list

Every collection uses one shape: `object: "list"`, the items under `data`, and a
`pagination` block. This holds for a full collection, a filtered result, a single item,
or an empty result.

```json theme={null}
{
  "object": "list",
  "data": [
    { "object": "message", "id": "msg_1", "direction": "outbound", "status": "sent" },
    { "object": "message", "id": "msg_2", "direction": "inbound", "status": "received" }
  ],
  "pagination": {
    "nextCursor": "msg_2",
    "hasMore": true
  }
}
```

Each item in `data` is a full object with its own `object` field. The `pagination` block
carries `nextCursor` (a string, or `null` on the last page) and `hasMore` (a boolean).
Smaller collections that fit in one response return `nextCursor: null` and
`hasMore: false`. To walk a large list, see [Pagination](/concepts/pagination).

<CodeGroup>
  ```bash title="cURL" theme={null}
  curl "https://api.dairo.app/v1/messages?limit=25" \
    -H "Authorization: Bearer $DAIRO_API_KEY"
  ```

  ```ts title="TypeScript" theme={null}
  const page = await dairo.messages.list({ limit: 25 });
  for (const message of page.data) {
    console.log(message.object, message.id); // "message" msg_…
  }
  console.log(page.pagination.nextCursor); // null on the last page
  ```

  ```python title="Python" theme={null}
  page = dairo.messages.list(limit=25)
  for message in page.data:
      print(message.object, message.id)
  print(page.pagination.next_cursor)  # None on the last page
  ```
</CodeGroup>

## Objects that carry a nested collection

A few detail endpoints return the base object plus a related collection, inlined as a
field. It is still one object with one `object` field:

* A thread detail carries its `messages`.
* A template detail carries the resolved `version`.
* An audience detail (`GET /v1/audiences/{id}`) carries its `members`, each an
  `object: "audience_member"`.
* A contact detail carries its `handles`, each an `object: "contact_handle"`.

An outbound message's delivery events are the exception: fetch them from
`GET /v1/messages/{id}/events` rather than inline on the message.

```json theme={null}
{
  "object": "thread",
  "id": "th_9fQ2",
  "subject": "Re: invoice",
  "messages": [
    { "object": "message", "id": "msg_1" },
    { "object": "message", "id": "msg_2" }
  ]
}
```

## Create responses and one-time secrets

A create returns the new object flat, with its `object` discriminator, at `201`. When the
create mints a secret, the secret rides on the object as a field, alongside
`secretShownOnce: true`:

```json theme={null}
{
  "object": "webhook",
  "id": "wh_7bK1",
  "url": "https://example.com/hooks/dairo",
  "events": ["message.received"],
  "signingSecret": "whsec_2bXr9f",
  "secretShownOnce": true
}
```

The field name depends on the resource: a webhook returns its `signingSecret`
(`whsec_…`), an API key returns its `secret` (`dairo_live_…` or `dairo_test_…`). The value
is shown once, at creation, and never returned again. Capture it immediately.

<CodeGroup>
  ```ts title="TypeScript" theme={null}
  const key = await dairo.apiKeys.create({ name: "worker", scopes: ["messages:send"] });
  console.log(key.id);     // key_… (safe to store and display)
  console.log(key.secret); // dairo_live_… (shown once)
  ```

  ```python title="Python" theme={null}
  key = dairo.api_keys.create(name="worker", scopes=["messages:send"])
  print(key.id)      # key_…
  print(key.secret)  # dairo_live_… (shown once)
  ```
</CodeGroup>

## Status codes and deletes

The response shape pairs with predictable HTTP status codes:

| Operation                                                                                                               | Status         | Body                            |
| ----------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------- |
| Create a resource (`POST /v1/inboxes`, `/domains`, `/webhooks`, `/api-keys`, `/audiences`, `/templates`, `/agents`)     | `201`          | the created object              |
| Read or update (`GET`, `PATCH`, `PUT`)                                                                                  | `200`          | the object, or a list           |
| Send or action `POST` (send a message, broadcast to an audience, replay events, enqueue an erasure job, verify, cancel) | `200` or `202` | the resulting object            |
| Delete (`DELETE /v1/{resource}/{id}`)                                                                                   | `200` or `204` | `{ "deleted": true }`, or empty |

A message send returns `200`, whether it goes out immediately or is scheduled;
broadcasting to an audience returns `202`, since Dairo expands and delivers it in the
background. Most deletes return `200` with `{ "deleted": true }`, and some also echo the
removed `id`. Deleting a domain or a share link returns `204` with no body.

## Errors

Errors use their own shape, `{ "error": { "type", "code", "message", "param" } }`,
covered in full on the [Errors](/concepts/errors) page.
