# Changelog

Everything that has changed in this API, newest first. Check here before you
ship — new capabilities show up in this list before you would notice them
anywhere else.

### Versioning

The `version` field of this document (currently **1.14.0**) moves with the API
and follows semantic versioning:

| Bump | Means |
|---|---|
| **MAJOR** | A breaking change — a field or endpoint removed, a response shape changed, a previously optional field made required. |
| **MINOR** | A new endpoint, a new optional field, a new accepted value. Existing integrations keep working untouched. |
| **PATCH** | Documentation corrections and bug fixes with no contract change. |

**Nothing is removed without warning.** A field on its way out is first marked
`deprecated` in this reference and listed under *Deprecated* below, and it keeps
working for at least one MINOR cycle. Deprecated fields are still accepted, so
an integration that has not migrated yet will not start failing.

Two habits keep you safe against MINOR releases: **ignore response fields you
do not recognise** rather than validating strictly against a fixed shape, and
**never assume a list field is a merge** — several (`geo_rules`, Shield's
`rules`) replace the whole array.

---

### 2026-10-04 - 1.14.0

**Added - stored integration reads and MCP guidance**
Eight GET routes under /api/v2/integrations expose connections, metrics,
tracking links, account summaries, statements and fan health. Explicit
integrations.read and current project/account rights apply. The Integrations
and MCP sections explain common revenue readings, freshness, paging,
scheduling limits and the read-only review workflow. This documents the
implemented contract; deployment and account eligibility must be verified.

---

### 2026-08-06 — 1.13.0

**Added — folders are fully manageable from the API**
`POST /api/v1/folders` creates one, and `GET`, `PATCH` and `DELETE
/api/v1/folders/{folder_id}` read, rename and delete it. Only the folder
*listing* existed before, so an integration could see folders but never make
one. Permissions: `folders.create`, `folders.read`, `folders.edit`,
`folders.delete` — these already existed on your API keys and finally have
endpoints behind them.

**Added — file a link into a folder when you create or update it**
`PUT /api/v1/links` and `PATCH /api/v1/links/{link_id}` accept `folder_id`
(single folder, `null` to clear) and `folders` (the full array — a link can
sit in several folders at once; sending it replaces the whole list). Links
created through the API no longer land outside every folder waiting to be
dragged into place by hand. Sending both spellings at once, or a folder id
from another project, is a `400`. See the new **Folders** section above.

**Added — the folders of a link are returned**
`link.folders` (an array of folder ids) is now on `GET /api/v1/links`,
`GET /api/v1/links/{link_id}`, and echoed by both write verbs. It is always
present — `[]` for a link outside every folder — and is exactly the shape you
send back on a `PATCH`. `GET /api/v1/links/{link_id}` additionally returns a
sibling `folders` array with the **names** resolved.

**Added — `?folder_id=` on the link list**
`GET /api/v1/links?folder_id=...` returns only the links inside one folder.

---

### 2026-08-03 — 1.12.0

**Added — Media previews on every asset**
`GET /api/v1/assets` and `GET /api/v1/assets/{file_id}` now return
`thumbnail_url` (320px), `preview_url` (1024px), `media_kind`
(`image`/`video`/`audio`/`document`) and `is_image`. Render a gallery
straight from those URLs instead of hand-building CDN transformations. The
two preview URLs are `null` for anything that is not an image — video and
PDF have no server-rendered still frame, so branch on `media_kind`.

**Added — `sort` on the asset list**
`?sort=newest|oldest|largest|smallest|name` (default `newest`). Anything else
is a `400`.

**Fixed — the asset list is finally paginable**
`total` used to repeat the size of the page you just received; it now counts
every asset matching your filters. `limit`, `offset`, the new `count` (this
page) and `has_more` come back with it. **If you were treating `total` as
"assets on this page", switch to `count`.**

**Fixed — `file_id` is back in the list, and reaches every file**
The list endpoint never returned `file_id`, so there was no way to go from a
listing to `GET /api/v1/assets/{file_id}`. It is returned again — and both
endpoints now resolve dashboard-uploaded files too (their UUID is stored
under `provider_file_id`), which previously 404'd on the detail route.

**Fixed — `?file_type=` no longer hides files with a generic MIME type**
Files stored as `application/octet-stream` are now matched on their filename
extension as well, so `?file_type=video` returns the whole set.

**Changed — `user_id` removed from the asset detail response**
An internal identifier that the list endpoint never exposed. No other field
changed.

---

### 2026-08-03 — 1.11.1

**Documentation — what an API-created link inherits**
A new section, *Links created through the API*, answers the question directly:
a link created with `PUT /api/v1/links` is the same object as one created in
the dashboard, with the same landing-page, Shield, template, Geo Filter and
Privacy capabilities, and it stays editable in the dashboard afterwards. The
one difference is **ownership** — an API key is a project credential, so the
link belongs to the project rather than to a team member. That matters only
for dashboard permissions on teams using per-member roles, and is spelled out
there. No API change.

---

### 2026-08-03 — 1.11.0

**Fixed — `GET /api/v1/trending-links` now actually exists**
This endpoint has been documented for a while but was never wired up: calling
it returned a 404 HTML page. It is live now, behind the
`statistics.read_project` permission, and returns exactly the payload
described below. The project is resolved from your API key — `project_id` is
optional, and sending another project's id is a `403` rather than a
cross-project read.

---

### 2026-08-03 — 1.10.0

**Added — Link Privacy (`privacy.links`)**
Make a link **private** from the API, the same switch as *Privacy* in the
dashboard: `{"privacy": {"links": true}}` on `PUT /api/v1/links` and
`PATCH /api/v1/links/{id}`. The destinations the link renders are then served
through a short redirect on your own domain (`https://<your-domain>/go/<token>`),
so the real URL never appears in the HTML a crawler reads. The call that flips
the switch provisions those redirects — there is nothing else to enable.
Send `{"links": false}` to go back to normal.

The flag is **returned by every link read** — `GET /api/v1/links`,
`GET /api/v1/links/{id}` and the `PUT /api/v1/links` response — always as
`privacy: { links: <boolean> }`, `false` on a link that was never made
private. See the **Link Privacy** section.

### 2026-08-02 — 1.9.0

**Added — every template family is now selectable on a link**
A link has four template slots, and until now only the landing one could be
set through the API. All four are now accepted on `PUT /api/v1/links` and
`PATCH /api/v1/links/{id}`, and returned together under `templates` by
`GET /api/v1/links/{id}`:
`cs_template` (landing), `cs_1-step` (first-step gate), `cs_3dots_template`
(direct 3-dots overlay) and `cs_3dots_click_template` (click 3-dots overlay).
Send `null` on any of them to detach. See *Templates — the three families*.

**Added — `?kind=` on the Templates endpoints**
`GET /api/v1/templates` and `GET /api/v1/templates/{template_id}` take
`kind=landing|first_step|three_dots`, so the ids for the new slots are
discoverable. Defaults to `landing`, so existing calls are unchanged. Both
responses now echo the `kind` they served. `GET /api/v1/templates` also takes
`?summary=true` for a trimmed listing — recommended, since the default
response returns full template documents and a v2 template embeds a whole page.

**Changed — template ids are checked against your project**
A template id that does not belong to the authenticated project is now
rejected with `400` naming the field, on every slot. It was previously
accepted for `cs_template`, which let a link be pointed at another project's
template. If you send ids you did not get from `GET /api/v1/templates`, check
them before upgrading.

**Fixed — `PATCH /api/v1/links/{id}` accepts `cs_template`**
It previously silently ignored it; the only way to attach a landing template
to an existing link was `PUT|PATCH /api/v1/links/{id}/dynamic-overrides`,
which still works and remains the right call when you set the template and
its `dynamic_informations` / `dynamic_links` together.

**Fixed — Templates response shapes documented correctly**
`GET /api/v1/templates` returns `{ templates, kind, project }` and
`GET /api/v1/templates/{template_id}` returns `{ template, kind, project }`.
This reference previously described a `{ success, data }` envelope neither of
them has ever returned. No API change — the documentation was wrong.

---

### 2026-08-01 — 1.8.0

**Added — Geo Filters (`geo_rules`)**
Per-visitor overrides on a link: block a country, redirect a region to a
localized destination, or serve a different landing page per browser language.
Settable on `PUT /api/v1/links` and `PATCH /api/v1/links/{id}`, returned in full
by `GET /api/v1/links/{id}`, and summarised as `geo_rules_count` on
`GET /api/v1/links`. See the **Geo Filters** section for the model and examples.

**Added — Shield (`/api/v1/links/{link_id}/shield`)**
Traffic filtering and cloaking as a first-class resource: read, replace (`PUT`),
patch, and disable (`DELETE`) a link's Shield configuration, plus
`GET /api/v1/shield` for the condition vocabulary, `GET /api/v1/shield/presets`
for the built-in presets, and `GET /api/v1/shield/bots` for the crawler
registry. See the **Shield** section.

**Deprecated — `geolocation_enabled`, `geolocation_redirects`**
These two fields on `PATCH /api/v1/links/{id}` were never read by the serve
layer: setting them did nothing, and links "configured" with them were not
geo-targeted at all. They are still accepted so existing callers do not break,
but they are now discarded rather than stored. **Migrate to `geo_rules`** — a
`{ countries, url }` entry becomes a rule with `t: "d_l"`.

**Fixed — link detail response documented correctly**
`GET /api/v1/links/{id}` returns `{ link, project }`. It was previously
documented as `{ success, data }`, which never matched the actual response. The
endpoint itself is unchanged; only the reference was wrong.

### 2026-07-24

**Added — Landing Pages (v2)**
Read and write Page Builder v2 pages on links and templates
(`/api/v1/links/{link_id}/landing`, `/api/v1/templates/{template_id}/landing`),
with version history, per-link `dynamic-overrides`, and the
`/api/v1/landing-engine` contract and example endpoints.

### 2026-03-31

**Added — Visit logs**
`GET /api/v1/logs`, `/api/v1/links/{link_id}/logs` and
`/api/v1/folders/{folder_id}/logs` — cursor-paginated visit history with masked
IPs and each visit's clicks merged in. See the **API Logs** section.

### 2026-02-19

**Added — Social networks & trending links**
Connected-account analytics, post metrics and history under
`/api/v1/social-networks`, plus `GET /api/v1/trending-links`.

### 2025-10-25

**Added — Folders**
`/api/v1/folders` and folder-scoped statistics.

### 2025-10-17

**Initial public API** — links, templates, assets and statistics.

---

> **Keeping this list current (internal note).** This changelog lives in the
> `info.description` of `static/openapi.yaml` and nowhere else — there is no
> second copy to drift out of sync. When you change the API: add a dated entry
> at the top of the list under the right label (**Added** / **Changed** /
> **Deprecated** / **Removed** / **Fixed**), say what a consumer must *do*
> rather than what the code now does, and bump `info.version` plus the number
> quoted under *Versioning* above.
