# Changelog

> Customer-facing changes to the Tailend API, newest first.

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

What changed in the Tailend API, for people calling it. Additive changes
(new optional parameters, new fields) can ship at any time; see
[Audience & versioning](https://api.tailend.dev/docs/audience-and-versioning.md) for what v1 promises
not to break.


## 2026-10-13

### Changed: Try any request from the API reference

Every endpoint in the API reference now has a Try it panel. Without a key it
shows a sample response; paste a key to send the request live and see the
status, headers and body. The standalone playground is retired, and its links
forward to the reference.

### Added: A status page

`https://api.tailend.dev/status` is now a page: overall status, API and docs
status, data freshness per city, 90-day API uptime and past incidents.
Programs keep getting JSON from the same URL (any `Accept` other than
`text/html`), and `/status.json` is the JSON explicitly. Both refresh once a
minute.

- JSON: `meta` gains `status`, `components`, `uptime` and `incidents`.
- Changed: `meta.unavailable[]` entries are now `{ what, reason }` in plain
  words, and `last_successful_ingest.source` describes where the time comes from
  instead of naming internal objects.

## 2026-10-12

### Added: Teams on events

Events carry `teams`: the local pro sports teams an event is built around
(watch parties, game-day themed events, team or mascot appearances). It is
always an array, empty when none. Values today: `eagles`, `phillies`,
`sixers`, `flyers`, `union`.

```bash
curl -G -H "X-API-Key: $TAILEND_API_KEY" --data "team=eagles,phillies" \
  https://api.tailend.dev/v1/philadelphia/events
```

- `team=` works on `/v1/{metro}/events`, the events routes under places and
  organizations, `/events/calendar` and `/search`.
- One value or a comma-separated list, matching any.
- An unknown value is a `400` that lists the allowed ones.
- On `/search`, `team=` limits the results to events.
- Available on every plan.

## 2026-10-10

### Changed: `q` also finds organization and place names

`q` on `/v1/{metro}/events`, `/classes`, `/camps`, `/places` and `/search`
now also matches the name of the organization behind a listing and the place
it happens at, tolerating spacing, punctuation and small typos. Searching for
an organization's name now finds its listings even when their own names do not
mention it.

## 2026-10-08

### Added: Related listings with `include=related`

`GET /v1/events/{id}`, `/v1/classes/{id}` and `/v1/camps/{id}` (and their
`slug` forms) accept `include=related`, which adds
`related: {similar, from_organization, at_place}`: up to six compact cards
each, so a listing page gets its "more like this" rows in one request.

- Related cards are listings the list endpoints would also return to your key.
- An add-on entitled like `include=source` (partner plans, or granted on a
  standard key).
- `400` on lists, search and places.
- If a list cannot be produced it comes back empty with a
  `related_unavailable` notice.

See [Shaping responses](https://api.tailend.dev/docs/shaping-responses.md#related-listings-includerelated).

## 2026-10-07

### Changed: Adult-only listings report `adults` in `ages.audience`

A listing flagged as adult-only content now reports `adults` in
`ages.audience` as well as `audience`, whatever its source page's wording.
Lists already left these out by default.

### Fixed: `ages.min` is `null` when ages are not stated

`ages.min` is `null` when a listing does not state its ages, instead of `0`.
A stated `0` (for example 0 to 5) is unchanged, and `age=` still keeps
listings that do not state their ages.

## 2026-10-06

### Added: Event calendar counts

`GET /v1/{metro}/events/calendar?from=…&to=…` returns, for every date in a
window of up to 62 days, how many occurrences start that day, how many are
free, and a count per category. The counts match the expanded event list
exactly.

```bash
curl -G -H "X-API-Key: $TAILEND_API_KEY" \
  --data "from=2026-11-01" --data "to=2026-11-30" \
  https://api.tailend.dev/v1/philadelphia/events/calendar
```

With `include=forecast` on eligible paid plans, today and the next seven days
also carry a day forecast.

### Added: One item per occurrence with `occurrences=expand`

`GET /v1/{metro}/events?occurrences=expand&from=…&to=…` lists one item per
occurrence, for calendar views: a recurring event appears on every day it
happens in the window (up to 31 days), each item carrying an `occurrence`
block with its own start, end and position in the series. Local times hold
across daylight-saving changes. Without `occurrences=expand`, `/events` is
unchanged.

### Added: Fetch listings by id with `ids=`

`/v1/{metro}/events`, `/classes`, `/camps` and `/places` accept `ids=` (up to
100 listing ids, comma-separated) to fetch specific listings in one call.
Results come back in the order given, with no date window and no paging. Ids
that are unknown, of another type, or no longer active are listed in
`meta.missing_ids`.

### Added: Classes starting in a date range

`/v1/{metro}/classes` accepts `starts_from` and `starts_to` (local dates,
inclusive, either alone) to keep classes whose term starts in the range, for
"classes starting this week". For programs with `sections`, any section
starting in the range matches, and `sections` are returned whole.

### Added: Indoor, outdoor and sensory-friendly facts

Events and classes carry `is_indoor`, `is_outdoor`, `good_for_rainy` and
`is_sensory_friendly`, facts about the place they happen at; places carry
`is_sensory_friendly`. `null` means the place does not say.

- Filter with `indoor=`, `outdoor=` and `sensory=`.
- A listing whose value is unknown matches neither `true` nor `false`.

## 2026-10-05

### Added: Classes with sections

Classes can list their variants as `sections` (days and times, age groups,
terms, each with its own tuition and availability).

- Class filters for weekday, age, time, dates and price match a single
  section at once.
- `openings=true` keeps programs with open seats.
- Programs published as several listings appear as one card, with the others
  under `sessions`; `expand=sessions` returns every class on its own.
- Section changes show up as updates in `/v1/changes` and the per-listing
  history.

See [Classes with sections](https://api.tailend.dev/docs/class-sections.md).

### Added: Event forecasts with `include=forecast`

Events can opt into `include=forecast` on eligible paid plans. Timed US
events in the next seven days receive either event-hour detail or a local-day
summary. Ineligible events omit the field; temporary availability problems are
reported through `notices[]`. See
[Shaping responses](https://api.tailend.dev/docs/shaping-responses.md#forecasts-includeforecast).

## 2026-09-30

### Added: Facets

Add `facets=` to an events, classes, camps or places list to get counts by
`category`, `weekday`, `time_block`, `price_band`, `age_band` and
`organization` over every listing that matches the filters, not just the page.
`limit=0` returns the counts alone. Available on every plan and counted as one
request.

```bash
curl -G -H "X-API-Key: $TAILEND_API_KEY" \
  --data "facets=category,weekday" --data "limit=0" \
  https://api.tailend.dev/v1/philadelphia/events
```

See [Market overview with facets](https://api.tailend.dev/docs/market-overview-with-facets.md).

### Added: Responses say which filters they used

Every list response (events, classes, camps, places, search, nearby and
around) carries `meta.applied_filters`, the value in effect for each filter,
and `meta.defaults_applied`, the names that came from a default rather than
your request. A smaller-than-expected result can now be explained from the
response itself.

- Events still cover the next 30 days by default.
- `min_confidence` still defaults to `0.7` (`0.5` on places).
- `adults` audiences are still left out, echoed as
  `audience: ["not_adults"]`, a value you can send back as
  `audience=not_adults`.

### Changed: Search understands word forms, related words and ages

On `GET /v1/{metro}/search`:

- `swim` finds `swimming`, and closely related family-activity words count:
  `pottery` finds `ceramics`, `baking` finds `cooking`.
- Question and filler words (`where can I`, `with`, `for`) no longer pull in
  unrelated listings or change a score.
- `5 year olds`, `ages 3-5`, `toddlers`, `baby` and `teens` narrow by age; the
  band applied is echoed in `meta.interpreted`. An `age` or `audience` you send
  yourself wins.
- Time and price words (`tonight`, `saturday`, `cheap`) are not filters yet;
  they are listed in `meta.ignored_words`.

See [Search](https://api.tailend.dev/docs/search.md).

### Added: Match levels on search results

Each search result carries `match`: `all_terms`, `related` or `partial`. The
`match` parameter selects which levels are returned (loose `partial` matches
are now opt-in), and `meta.match_counts` counts every level whatever you
selected. Search cursors from before this change are no longer accepted;
restart the search without the cursor.

### Changed: Search `score` is absolute

`score` on search results was scaled so the best result in each search read
1. It is now a 0 to 1 measure of how well one listing fits the query's words,
the same whatever else is returned, and comparable between queries.

### Changed: Finding an organization works the way people type

On `GET /v1/{metro}/organizations`, `q` finds a business by its name, its
website address or a pasted URL, ignoring casing, punctuation, spacing and
endings such as `LLC`, `Inc`, `& Co` and `.com`. `design gym`,
`Design Gym` and `designgym.com` all find `designgym.co`, and a small typo
still finds it.

- Results carry `score` (0 to 1) and `match` (`exact_name`, `exact_domain`,
  `name`, `domain` or `fuzzy`), best match first.
- A query of only `the`, `and`, `llc`, `.com` and the like returns an empty
  list with a `meta.hint`.
- Organization-search cursors from before this change are no longer accepted.

See [Finding a business](https://api.tailend.dev/docs/finding-a-business.md).

### Changed: `website_url` is filled in more often

When an organization has no website recorded, `website_url` now shows the
address of the site the organization itself runs, if we have one.

### Changed: Clearer meaning for class fields a provider may not state

The descriptions for `program.adult_participation`, `term.total_classes`,
`tuition.drop_in_price`, `tuition.trial_price`, `tuition.trial_notes`,
`registration.spots_remaining` and `registration.has_spots_available` now say
what `null` means: not stated by the source, never "no". The response shape is
unchanged.

## 2026-09-28

### Added: Unified listing search

`GET /v1/{metro}/search` searches events, classes, camps and places with the
same visibility and filter rules as the individual list endpoints.

```bash
curl -G -H "X-API-Key: $TAILEND_API_KEY" --data-urlencode "q=story time" \
  https://api.tailend.dev/v1/philadelphia/search
```

Very broad queries may set `meta.capped: true`; add filters or a more
specific `q` to narrow them. See [Search](https://api.tailend.dev/docs/search.md).

### Added: Recurring events listed by their next occurrence

A recurring event's `occurrence.next_starts_at` and `next_starts_local` give
the next live date, and `from`/`to` windows on `GET /v1/{metro}/events`
include a series by that next occurrence rather than only its original start
date. A series with no more occurrences stops appearing in lists; its
permalink still resolves by `id` or `slug`. See
[Recurring events](https://api.tailend.dev/docs/recurring-events.md).

### Added: Plan-level access for premium features

A key's effective entitlement is its plan's defaults plus whatever is granted
on the key itself. A premium endpoint without the entitlement answers
`403 feature_not_in_plan`, distinct from `include_not_permitted`.
`include=source` follows the same rule. Both `403 include_not_permitted` and
its `include_mode=lenient` notice now carry an upgrade link. See
[Plans & access](https://api.tailend.dev/docs/plans-and-access.md).

### Changed: Places default to `min_confidence=0.5`

Events, classes and camps keep the `0.7` default. Pass `min_confidence=0.7` on
a places call for the previous behaviour.

## 2026-09-27

### Fixed: Events with no date return `null`, not an empty string

`starts_at` and `starts_local` are `null` when an event has no date, and a new
`has_date` boolean tells you which case you're in without parsing either
field. If your code assumed `starts_at` was always a non-empty string, treat
`null` the way you'd already have treated `""`.

### Fixed: Listing version history shows public change facts only

Each item from `GET /v1/listings/{id}/versions` contains `changed_at`, a
closed `kind`, and public field names only. Raw changed values are no longer
returned.

### Fixed: `source.verification.status` lists its real values

With `include=source`, the documented values are `clear`, `changed`,
`flagged`, `unverifiable`, `unreachable` and `gone`. Two values that never
occur were removed.

## Earlier

### Changed: Vocabulary cleanup before launch

`organizer` is `organization` everywhere, a "venue" is a `place`,
`include=provenance` is `include=source`, distances are metres only, and
`GET /v1/{metro}/vitality` is `GET /v1/{metro}/activity`.

### Added: Audience on every listing

Every event, class, camp and place carries `audience` (`kids`, `families`,
`teens`, `adults`, `all_ages`). Adult-only content is excluded by default on
list endpoints; pass `audience=all` to include it.

### Added: Relationship routes

List a place's or organization's events, classes and camps directly
(`/v1/places/{id}/events`, and similarly for organizations), with every other
list parameter still available.

### Added: Seasons, links and images

- `traits.seasons` on events, classes and camps, with a matching `season=`
  filter.
- `links` on every resource: relative paths to the resource and its related
  collections.
- `images` on places: more photos beyond `image_url`.

### Changed: Lean default responses

Every resource carries a flat `confidence` and `verified_at`. Where the data
came from, and detailed verification status, moved behind `include=source`;
add `include_mode=lenient` to get a `200` instead of a `403` when your key
isn't entitled.

### Changed: New API key format

Keys are issued as `tl_<prefix>_<secret>`. Existing keys keep working.
