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

# API changelog

> Every change to the Periphery API contract, newest first.

Changes to the `/v1` contract, newest first. Additive changes (new fields, error codes, provider slugs) can ship at any time; removals, renames and changes of meaning are announced here, and by email to every account that holds an API key, at least 90 days ahead. See [Versioning and changes](https://docs.periphery.exposed/api/overview.md#versioning-and-changes).

**2026-11-05**

Launch contract

These changes shipped without notice because the API had not launched yet.

**Search moved to the apps collection**

* Search is now `GET /v1/apps?q=…`, the collection whose records are `GET /v1/apps/{hostname}`. `/v1/apps/` with a trailing slash is the same collection.
* `GET /v1/search` is removed, with no alias or redirect: it answers `404 not_found` like any unknown path.
* The page parameter `p` is renamed `page`. `p` is no longer read: `?q=acme&p=3` serves page 1.
* `q` is required: a missing or blank `q` answers `400 query_required` (`q is required: give a search query`) and uses no search. It used to return an empty page.

**Search response**

* New keys: `query`, the canonical query Periphery ran; `pageSize`, always `50`; and `hasMore`, `true` when the next page has at least one hit. Stop paging when `hasMore` is `false`.
* `totalCapped` is always present, `false` unless counting stopped at 1,000,000.
* `total` comes from a count kept for up to 10 minutes, raised to at least what the page served proves.
* A `q` that reads as nothing, such as `status:2oo` alone, returns `{"query": "", "page": 1, "pageSize": 50, "hasMore": false, "total": 0, "totalCapped": false, "hits": []}` and uses no search: the `""` query shows the filter was dropped.

**Search hits and app records**

* A search hit is now the first part of the app record: both share the same fields, with the same values for the same hostname. See [App fields](https://docs.periphery.exposed/api/overview.md#app-fields).
* Removed: `hasCreds` on search hits. Use `hasVerifiedCreds` and `verifiedDetectors`, which hits now carry too.
* Removed: `emailCount` on app records. Count `emails` instead.
* Removed: `htmlUrl` on app records. The raw page source is what the credential scan ran over, so it held every finding in full; it is no longer served anywhere, console included.
* New on search hits: `hasVerifiedCreds`, `verifiedDetectors`, and the signed `faviconUrl` and `screenshotUrl`, which expire after an hour.
* New on both: `scanned`, `true` when Periphery captured the page and stored a snapshot.
* `provider`, `status`, `title`, `lastSeen` and `hasVerifiedCreds` are always present on both, and `text` always on app records. `status` is `0` and `title` and `text` are `""` when there is no value.
* `emails`, `verifiedDetectors` and `credentials` are `[]` when Periphery looked and found nothing, and omitted when it never looked. They were omitted whenever empty.
* `faviconHash` is present whenever Periphery has the favicon, including a hash of `0`, and omitted otherwise.
* `verifiedDetectors` lists each detector once whatever its case, sorted alphabetically ignoring case.
* `lastSeen` means Periphery's last visit that did not prove the host dead, whether or not the host answered. It is not the capture time.
* An SVG favicon's `faviconUrl` downloads the file when opened directly. Showing it in an `<img>` is unaffected.
* JSON no longer escapes `&`, `<` and `>`: signed links contain a literal `&`.

**App records**

* `GET /v1/apps/{hostname}` answers `404 app_not_found` for hosts Periphery has never visited, as it already did for dead hosts: an app record exists exactly when search can return the app. The console app page does the same.
* The hostname is matched case-insensitively, a trailing dot is ignored, and Unicode hostnames are accepted. The response carries the lower-case ASCII form. A hostname that cannot be valid answers `404` straight away.

**Errors**

* Every error body carries a stable `code` next to the `error` message: `{"error": "…", "code": "…"}`. Switch on `code`; messages may change. See [Limits and errors](https://docs.periphery.exposed/api/limits-and-errors.md#error-codes).
* A failed API key lookup answers `502 unavailable` with `Retry-After: 5` instead of `503`.
* `Retry-After` is sent on `429 rate_limited` and `429 ip_rate_limited` (1 second), `502` (5), `503 busy` (2) and `503 maintenance` (3600).
* `401` responses carry `WWW-Authenticate`.
* A method other than `GET` on search (`/v1/apps`), including `HEAD` and `POST`, answers `405 method_not_allowed` with `Allow: GET`, before the key is checked, and uses no search. It used to run the search. On `/v1/apps/{hostname}`, `GET` and `HEAD` work as before, and other methods, which used to be served like `GET`, answer `405` too.
* A `page` too large to be a number, such as `99999999999999999999`, answers `400 page_out_of_range` (`page is past the last page (20000)`) instead of serving page 1.
* Unknown paths on the API host, under `/v1` or not, including `/v1` and the removed `/v1/search`, answer JSON `404 not_found`. No `/v1` path answers with a console page or a redirect.
* Plain `http://api.periphery.exposed` answers `403 https_required` instead of redirecting to HTTPS. A key sent that way travelled in clear text: rotate it.
* When Periphery is down, the API host answers JSON `502 unavailable`; during maintenance, JSON `503 maintenance` with `Retry-After: 3600`.
* Every response, errors included, carries `Cache-Control: no-store`.

**Queries**

* The 256 limit on `q` counts characters (Unicode code points), not bytes.
* A `lastSeen:` or `firstSeen:` window too large to represent, over about 292 years such as `lastSeen:<30501w`, is searched as text, as other unreadable filter values are. It used to wrap around to a much shorter window.

**Documentation**

* Credential findings only ever come masked. The `text` field and the screenshot show what the page displayed, so a secret printed on screen appears in them as shown.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.