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

# Conventions

# Conventions

Cross-cutting rules that apply across all Animo API v1 endpoints.

## Base URL and versioning

* All endpoints are prefixed with `/api/v1`.
* Versioning is URL-based only. There is no `Accept` header versioning.
* Future versions would use a new prefix (e.g. `/api/v2`).

## Identifiers

Public identifiers in API responses are **not** database primary keys:

| Resource    | ID format | Example field                 |
| ----------- | --------- | ----------------------------- |
| User        | sqid      | `id`                          |
| Company     | sqid      | `id` (path param `{company}`) |
| Event       | slug      | `id` (path param `{event}`)   |
| Activation  | slug      | `id`                          |
| Form        | sqid      | `id`                          |
| Form field  | sqid      | `id`                          |
| Order       | sqid      | `id`                          |
| Cohost      | sqid      | `id`                          |
| Ticket type | slug      | `slug`                        |
| Webhook     | UUID      | `id`                          |

Use the `id` (or `slug` for ticket types) returned by the API in subsequent requests. Route model binding resolves these public identifiers automatically.

## Timestamps

Datetime fields in responses use ISO 8601 with a `+00:00` offset. See [Timestamps (actual format)](#timestamps-actual-format) for the real shape — do not assume a `Z` suffix or fixed millisecond width.

**Input format for event dates** (create/update) uses a separate format: `Y-m-d H:i` (e.g. `2026-04-25 09:00`).

## Pagination

List endpoints return Laravel's standard paginated JSON:

```json theme={null}
{
  "data": [ ... ],
  "links": {
    "first": "https://{domain}/api/v1/{company}/events?page=1",
    "last": "https://{domain}/api/v1/{company}/events?page=3",
    "prev": null,
    "next": "https://{domain}/api/v1/{company}/events?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "per_page": 15,
    "to": 15,
    "total": 42
  }
}
```

Default page size is **15**. Use the `page` query parameter to navigate.

## Company context

Most resources are scoped under a company:

```http theme={null}
GET /api/v1/{company}/events
```

* `{company}` is the company **sqid** from `GET /api/v1/companies`.
* Middleware restricts all queries to that company.
* The authenticated user must belong to the company.
* The company must have an active **Pro** plan (enforced via the `USE_API` subscription gate).

## Subscription gates

API access is a paid feature. In the Animo admin, this is the **Pro** plan. Subscribe or upgrade at **[https://app.animo.co/admin/settings/billing](https://app.animo.co/admin/settings/billing)**.

| Internal gate      | Animo plan                       | Required for                                                    |
| ------------------ | -------------------------------- | --------------------------------------------------------------- |
| `USE_API`          | **Pro** (Stripe tier: `starter`) | All company-scoped REST resources (events, forms, orders, etc.) |
| `USE_INTEGRATIONS` | **Pro** and above                | Webhook attach/update/detach                                    |

Free and Business plans do **not** grant API access. Upgrade at **[https://app.animo.co/admin/settings/billing](https://app.animo.co/admin/settings/billing)**.

### Entitlement behavior

| Endpoint                                                    | Without Pro plan                                                                |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `GET /api/v1/companies`                                     | `200` with empty `data: []` — looks like "no companies", not a permission error |
| `GET /api/v1/{company}/…` (and other company-scoped routes) | `403 Forbidden`                                                                 |

If your token is valid but `GET /companies` returns an empty list, check the company's plan before debugging scopes or token setup.

## Public URLs

The API does not return public-facing URLs in responses today. Construct them from slugs in the API response.

### Event page

```
https://app.animo.co/{company-slug}/event/{event-slug}
```

| Segment          | API source                          |
| ---------------- | ----------------------------------- |
| `{company-slug}` | `slug` from `GET /api/v1/companies` |
| `{event-slug}`   | `id` from event resources           |

### Ticket signup page

Ticket types created via the API can be linked on the public Animo ticket shop. These URLs are separate from the API base URL and use **slugs**, not sqids.

**Pattern:**

```
https://app.animo.co/{company-slug}/event/{event-slug}/{ticket-type-slug}
```

| Segment              | Source           | API field                         |
| -------------------- | ---------------- | --------------------------------- |
| `{company-slug}`     | Company URL slug | `slug` on `GET /api/v1/companies` |
| `{event-slug}`       | Event URL slug   | `id` on event resources           |
| `{ticket-type-slug}` | Ticket type slug | `slug` on ticket type resources   |

**Optional query parameter:**

| Parameter | Description                                                   |
| --------- | ------------------------------------------------------------- |
| `qty`     | Pre-select ticket quantity on the signup page (e.g. `?qty=1`) |

**Example:**

```
https://app.animo.co/aidemodays/event/ai-demo-days-hr-edition/general-admission?qty=1
```

The ticket signup flow continues at `/form` on the same path when a registration form is attached to the ticket type.

### Order checkout page (paid tickets)

After creating a paid order via the API, send the attendee to Mollie checkout:

```
https://app.animo.co/{company-slug}/event/{event-slug}/o/{order-id}/checkout
```

| Segment      | API source                          |
| ------------ | ----------------------------------- |
| `{order-id}` | `id` from the order resource (sqid) |

`paid_at` remains `null` until Mollie payment succeeds (async webhook).

### Activation form page

To embed or link an Animo-hosted activation form (sponsorship, waitlist, contact) from a custom site, get the activation `slug` from `GET /api/v1/{company}/activations`. There are two URL variants. **Prefer the company-scoped URL by default** — it always works for published activations. Use the event-scoped form only when you need to attribute the signup to a specific event.

**Company-scoped (default):**

```
https://app.animo.co/{company-slug}/{activation-slug}
```

**Event-scoped** (when the activation is linked to an event):

```
https://app.animo.co/{company-slug}/{event-slug}/{activation-slug}
```

| Segment             | API source                                                                        |
| ------------------- | --------------------------------------------------------------------------------- |
| `{company-slug}`    | `slug` from `GET /api/v1/companies`                                               |
| `{activation-slug}` | `id` or `slug` from `GET /api/v1/{company}/activations`                           |
| `{event-slug}`      | `id` from the linked event (**not** returned on `ActivationResource` — see below) |

> **Unlike tickets, event activations do *not* use `/event/` in the path** — it's `/{company}/{event}/{activation}`, not `/{company}/event/{event}/{activation}`.

**Sub-paths** (the flow, same as tickets): the base URLs above are the entry point. `/form` (form step) and `/thank-you` (confirmation) are reached *after* sign-up — link cold traffic to the **base URL, not `/form`**; visitors who hit `/form` without signing up are redirected back to the base URL. An event-scoped URL whose activation isn't actually attached to that event also redirects to the company-scoped URL.

**API gap:** `ActivationResource` does not expose the linked event's slug, so you can't build the event-scoped URL from `GET /activations` alone — fetch the event slug from `GET /events`, or use the company-scoped URL, which always works.

### Slug behavior (events vs ticket types)

| Resource    | On create                                                        | On update                         |
| ----------- | ---------------------------------------------------------------- | --------------------------------- |
| Event       | `id`/`slug` derived from `name` — supplied `slug` is **ignored** | `slug` can be changed via `PATCH` |
| Ticket type | Supplied `slug` is **honored**                                   | `slug` can be changed via `PATCH` |

## Timestamps (actual format)

Responses use ISO 8601 datetimes with a `+00:00` UTC offset, not a `Z` suffix:

```
2026-04-25T09:00:00.4040+00:00
```

Fractional-second precision is **variable** (e.g. `.4040`, `.011`, `.088`). Do not assume exactly three millisecond digits. Parse as ISO 8601 with flexible fractional seconds.

## Search filter

Several list endpoints support a `q` query parameter for case-sensitive substring search:

| Endpoint                            | Searches |
| ----------------------------------- | -------- |
| `GET /api/v1/{company}/activations` | `title`  |
| `GET /api/v1/{company}/events`      | `name`   |
| `GET /api/v1/{company}/forms`       | `name`   |

## Optional includes

| Query parameter      | Endpoints             | Effect                                             |
| -------------------- | --------------------- | -------------------------------------------------- |
| `include_form`       | Ticket type list/show | Embeds the related form and fields                 |
| `include_submission` | Order list/show       | Embeds submission (with form and answers) and lead |

## Request bodies

* Send JSON with `Content-Type: application/json`.
* `PATCH` endpoints accept partial updates — only include fields you want to change.
* `POST` create endpoints use paths like `/create` (e.g. `POST /api/v1/{company}/events/create`).

## Validation errors

Invalid request bodies return `422 Unprocessable Entity`:

```json theme={null}
{
  "message": "The name field is required. (and 1 more error)",
  "errors": {
    "name": [
      "The name field is required."
    ],
    "date_start": [
      "The date start field is required."
    ]
  }
}
```

Refer to each endpoint page for field requirements and validation rules.

## Response envelope

Single resources are wrapped in a `data` key:

```json theme={null}
{
  "data": {
    "id": "my-event",
    "name": "My Event"
  }
}
```

Collections (non-paginated) follow the same pattern. Paginated collections use the structure described in [Pagination](#pagination).

## Related

* [Authentication](authentication.md)
* [API index](index.md)
