# Conventions

> Time fields, ids, booleans, prices and the response envelope every endpoint shares.

Source: https://api.tailend.dev/docs/conventions

Every endpoint follows the same rules for time, ids, booleans, prices and the
response envelope. Learn them once here.

## Requests

- Base URL: `https://api.tailend.dev`. Every data endpoint is a `GET` under
  `/v1`; the API is read-only, and any other method returns `405`.
- Send your key in the `X-API-Key` header. See
  [Authentication](https://api.tailend.dev/docs/authentication.md).
- Most paths start with a metro slug: `/v1/philadelphia/events`. Get the list
  from `GET /v1/metros`.
- List parameters take several values, comma-separated or repeated:
  `audience=teens,families` is the same as `audience=teens&audience=families`.

## The response envelope

A list returns `data` (an array) and `meta`. A single read returns `data` (an
object). Some responses also carry `notices[]`: things you should know that
did not stop the request, such as an add-on your key is not entitled to.

```json
{
  "data": [{ "id": "…", "type": "event", "name": "…" }],
  "meta": { "limit": 50, "count": 1, "has_more": false, "next_cursor": null },
  "notices": []
}
```

Every failure uses one error envelope instead. See [Errors](https://api.tailend.dev/docs/errors.md).

## Time

| Field suffix | Meaning | Example |
| --- | --- | --- |
| `*_at` | An instant in UTC, RFC 3339 | `starts_at: "2026-09-14T13:30:00.000Z"` |
| `*_local` | Local wall-clock time with no offset; read it against the object's `timezone` | `starts_local: "2026-09-14T09:30:00"` |
| `*_on` | A calendar date with no time | `starts_on: "2026-09-14"` |

- `starts_local` is already the time printed on the source page. Never add an
  offset to it.
- No field carries a `_utc` suffix: UTC is what `_at` means.
- Date filters such as `from` and `to` are local calendar dates in the metro's
  timezone, inclusive.
- Some events have no date at all. They have `has_date: false`, and
  `starts_at` and `starts_local` are `null`. Check `has_date` before parsing
  either.

## Ids and slugs

Every listing has a stable `id` (a UUID) and a human-readable `slug`. Read a
single listing by either: `GET /v1/events/{id}` or `GET /v1/events/slug/{slug}`.
Every resource also carries `links`, relative paths to itself and its related
collections.

## Booleans

Yes-or-no facts are named `is_*` or `has_*` (`is_free`, `is_recurring`,
`has_date`, `has_time`). When a source does not say, the value is `null`: read
`null` as "unknown", never as "no".

## Prices

A price is a decimal amount plus a currency, never a formatted string:

```json
{ "price": { "is_free": false, "min": 12, "max": 12, "currency": "USD" } }
```

## Distances

Distances and radii are in metres: `radius_m`, `distance_m`.

## Organizations, places and sources

- An **organization** runs listings (`organization`).
- A **place** is where a listing happens (`place`).
- A **source** is the page a listing was read from, available with
  `include=source` on entitled keys. See [Shaping responses](https://api.tailend.dev/docs/shaping-responses.md).

## Stability

v1 is frozen: every path, parameter and field in the reference is a promise.
New optional parameters, new fields and new `error.code` values can appear at
any time, so parse permissively. See
[Audience & versioning](https://api.tailend.dev/docs/audience-and-versioning.md).
