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

# JSON-API Conventions

The anny API follows the [JSON-API v1.1 specification](https://jsonapi.org/). This guide covers the conventions you need to know when making requests.

***

## Content Type

Write operations use the JSON-API media type:

```http theme={null}
Content-Type: application/vnd.api+json
Accept: application/vnd.api+json
```

Standard `application/json` is **not** accepted for `POST` and `PATCH` operations.

For `GET` requests, the API also accepts:

```http theme={null}
Accept: application/json
```

Some read-oriented and UI-oriented endpoints return normalized `application/json` payloads instead of JSON-API resources. Webhook deliveries are always sent as `application/json`.

***

## Resource Objects

Every API response wraps data in a JSON-API resource object:

```json theme={null}
{
  "data": {
    "type": "bookings",
    "id": "a1b2c3d4-...",
    "attributes": {
      "start_date": "2026-04-01T10:00:00+02:00",
      "end_date": "2026-04-01T11:00:00+02:00",
      "status": "accepted"
    },
    "relationships": {
      "resource": {
        "data": { "type": "resources", "id": "r1" }
      },
      "service": {
        "data": { "type": "services", "id": "s1" }
      }
    }
  }
}
```

Collection responses return `"data": [...]` as an array.

***

## Common Headers

| Header            | Required                  | Description                                                                         |
| ----------------- | ------------------------- | ----------------------------------------------------------------------------------- |
| `Authorization`   | Yes (admin/customer APIs) | `Bearer {access_token}` — see [Authentication](/developers/guides/authentication)   |
| `Content-Type`    | Yes (POST/PATCH)          | `application/vnd.api+json`                                                          |
| `Accept`          | Recommended               | `application/vnd.api+json`                                                          |
| `Accept-Language` | Optional                  | Locale code (e.g. `de-DE`, `en-US`). Controls translated fields and error messages. |

### Tenant Context

Admin API requests require an organization context. Pass it as a query parameter:

```http theme={null}
GET /api/v1/bookings?o={organization_id}
```

The backend resolves the active organization from:

1. The `tenant` claim in the JWT (if present), or
2. The `o` query parameter

For explicit control, always include `?o={organization_id}`.

***

## Including Related Resources

Use the `include` parameter to sideload related resources in a single request, avoiding N+1 round-trips:

```http theme={null}
GET /api/v1/bookings?include=resource,service,order.customer
```

Nested relationships use dot notation. The response adds an `included` array:

```json theme={null}
{
  "data": { "type": "bookings", "id": "b1", "..." : "..." },
  "included": [
    { "type": "resources", "id": "r1", "attributes": { "..." : "..." } },
    { "type": "services", "id": "s1", "attributes": { "..." : "..." } }
  ]
}
```

<Note>
  Each resource type defines its own set of allowed include paths. Requesting an unsupported path returns a `400` error.
</Note>

***

## Sparse Fieldsets

Request only the attributes you need to reduce payload size:

```http theme={null}
GET /api/v1/bookings?fields[bookings]=start_date,end_date,status&fields[resources]=name
```

This returns only the listed attributes for each resource type.

***

## Filtering

Filter collections using the `filter` query parameter.

### Simple Filters

```http theme={null}
GET /api/v1/bookings?filter[status]=accepted
```

### Multiple Values

Repeat the filter key with `[]` to pass multiple values as an OR condition:

```http theme={null}
GET /api/v1/bookings?filter[status][]=accepted&filter[status][]=requested
```

### Date Range Filters

Range filters use nested `from` and `to` keys:

```http theme={null}
GET /api/v1/bookings?filter[start_date_range][from]=2026-01-01&filter[start_date_range][to]=2026-03-31
```

### Search

Full-text search across indexed fields:

```http theme={null}
GET /api/v1/customers?filter[search]=jane
```

### Custom Field Filters

Filter by custom field values using the field's UUID:

```http theme={null}
GET /api/v1/customers?filter[custom_fields][{FIELD_UUID}][f_id]={FIELD_UUID}&filter[custom_fields][{FIELD_UUID}][o]==&filter[custom_fields][{FIELD_UUID}][v]=Premium
```

Each custom field filter entry requires:

| Key    | Description                                                            |
| ------ | ---------------------------------------------------------------------- |
| `f_id` | Custom field UUID                                                      |
| `o`    | Operator: `=`, `<>`, `<`, `>`, `<=`, `>=`, `in`, `range`               |
| `v`    | Value to match (comma-separated for `in`, colon-separated for `range`) |

<Note>
  Available filters vary per resource. Requesting an unsupported filter returns a `400` error.
</Note>

***

## Sorting

Sort results with the `sort` parameter. Prefix with `-` for descending:

```http theme={null}
GET /api/v1/bookings?sort=-start_date
```

Multiple sort fields are comma-separated:

```http theme={null}
GET /api/v1/bookings?sort=status,-start_date
```

<Note>
  Each resource type defines its allowed sort parameters. The default sort varies per resource.
</Note>

***

## Pagination

The API uses offset-based pagination:

```http theme={null}
GET /api/v1/bookings?page[number]=1&page[size]=25
```

| Parameter      | Default | Description          |
| -------------- | ------- | -------------------- |
| `page[number]` | `1`     | Page index (1-based) |
| `page[size]`   | `15`    | Items per page       |

### Response Metadata

Paginated responses include navigation links:

```json theme={null}
{
  "data": [ "..." ],
  "meta": {
    "page": {
      "current-page": 1,
      "per-page": 15,
      "from": 1,
      "to": 15,
      "total": 142,
      "last-page": 10
    }
  },
  "links": {
    "first": "/api/v1/bookings?page[number]=1&page[size]=15",
    "last": "/api/v1/bookings?page[number]=10&page[size]=15",
    "next": "/api/v1/bookings?page[number]=2&page[size]=15",
    "prev": null
  }
}
```

***

## Creating and Updating Resources

<Tabs>
  <Tab title="Create (POST)">
    ```http theme={null}
    POST /api/v1/bookings

    {
      "data": {
        "type": "bookings",
        "attributes": {
          "start_date": "2026-04-01T10:00:00+02:00",
          "end_date": "2026-04-01T11:00:00+02:00"
        },
        "relationships": {
          "resource": {
            "data": { "type": "resources", "id": "r1" }
          },
          "service": {
            "data": { "type": "services", "id": "s1" }
          }
        }
      }
    }
    ```
  </Tab>

  <Tab title="Update (PATCH)">
    Include the `id` in both the URL and the body:

    ```http theme={null}
    PATCH /api/v1/bookings/b1

    {
      "data": {
        "type": "bookings",
        "id": "b1",
        "attributes": {
          "status": "canceled"
        }
      }
    }
    ```

    Only include attributes that are changing — omitted attributes are left unchanged.
  </Tab>
</Tabs>

***

## Error Responses

Errors follow the JSON-API error format:

```json theme={null}
{
  "errors": [
    {
      "status": "422",
      "title": "Unprocessable Entity",
      "detail": "The start date must be before the end date.",
      "source": {
        "pointer": "/data/attributes/start_date"
      }
    }
  ]
}
```

### Common Status Codes

| Code  | Meaning                                                            |
| ----- | ------------------------------------------------------------------ |
| `200` | Success                                                            |
| `201` | Resource created                                                   |
| `204` | Success (no content)                                               |
| `400` | Bad request (invalid query parameters, unsupported include/filter) |
| `401` | Unauthorized (missing or invalid token)                            |
| `403` | Forbidden (insufficient permissions)                               |
| `404` | Resource not found                                                 |
| `409` | Conflict (e.g. duplicate, concurrent edit)                         |
| `422` | Validation error (check `errors[].detail` for specifics)           |
| `429` | Rate limited                                                       |

***

## Combining Parameters

All query parameters can be combined in a single request:

```http theme={null}
GET /api/v1/bookings?o=42&include=resource,service&filter[status]=accepted&filter[start_date_range][from]=2026-04-01&sort=-start_date&page[size]=25&fields[bookings]=start_date,status
```
