Endpoints
Both are described in full, with a playground, under Endpoints in the sidebar.
/v1/apps (or /v1/apps/) takes GET only; any other method, HEAD included, answers 405. Any other path under /v1 answers 404; see Limits and errors.
Every response, errors included, is JSON with Content-Type: application/json and Cache-Control: no-store.
A first call
401, but its page has not been captured yet, so it has no snapshot links, no emails and no verifiedDetectors.
The response
Every key is always present.
q is required. A missing or blank q answers 400 and uses no search:
q that reads as nothing, such as status:2oo alone, returns an empty page whose query is "", showing that the filter was dropped, and uses no search:
App fields
A search hit and an app record share the same fields, with the same values for the same hostname. The app record addstext and credentials.
The rules behind the table: strings, numbers and booleans are always present. A timestamp is omitted when the event never happened. A list is
[] when Periphery looked and found nothing, and omitted when it never looked. Signed links and faviconHash are omitted when there is nothing to link to. No field is ever null.
scanned: false means Periphery has visited the host but holds no snapshot of its page yet: every render so far failed or hit a bot wall. Its status, if any, comes from a fallback request. Such an app has no snapshot links and no lists, and its title and text are empty.
Signed links
faviconUrl and screenshotUrl are signed and expire after an hour: download what you want to keep. To refresh the links of one app, call GET /v1/apps/{hostname}: it returns fresh links without using a search, though it shares the one-request-per-second budget.
Pagination
Search results come in pages of 50. Request another page withpage:
hasMore is true, and stop when it is false. hasMore is exact: it is true only when the next page has at least one hit. A page past the end returns an empty hits array. A page that is not a number, or is below 1, is read as 1. Page 20,000 is the last one, and its hasMore is always false; a larger page answers 400 page_out_of_range.
Each page is one search against your monthly allowance, and the per-second rate limit applies. Pages up to 20,000 are allowed, but 3,000 searches a month read at most 3,000 pages, or 150,000 hits; narrow the query to reach what you need in fewer pages.
total comes from a count that Periphery keeps for up to 10 minutes, raised to at least what the page you are reading proves. Treat it as an estimate for progress bars, not as a stop rule. Counting stops at 1,000,000: past that, total reads 1000000 and totalCapped is true.
Order. A query with free text is ordered by relevance. A query of filters only is ordered by firstSeen, newest first, then by lastSeen, newest first; apps with no firstSeen come last.
Pages are offsets over live data. Apps enter and leave the index between your requests, so one app can appear on two pages, or move past you between them. De-duplicate by hostname. Each page is cached for up to a minute, and total for up to 10 minutes. GET /v1/apps/{hostname} reads the record live: when it disagrees with a search hit, the app record is the current one.
The query field
query shows the query Periphery actually ran, in the canonical form of the query syntax. Filter values it could not read are dropped or searched as text, so compare it with what you sent to catch a typo:
status:abc was dropped, acme.com became a domain search, and 7d was written as 1w.
A value searched as text comes back as you sent it, with the free text at the start of query, so it can read like a filter. Filters follow the free text in a fixed order, and lastSeen: and firstSeen: windows come last. A window that applies always has < or >, a whole number of at least 1 and a unit of h, d or w, and is at most 15,250 weeks; a lastSeen: or firstSeen: token without those, or followed by anything other than another window, was searched as text:
Credentials in responses
Every API key belongs to a Plus account, so responses carry what Plus sees: email addresses on search hits and app records, and on an app record thecredentials array with every finding, verified or not, each value masked. hasVerifiedCreds and verifiedDetectors summarise it. The has:creds filter in q always applies. See Emails and credentials.
Versioning and changes
The/v1 contract changes in two ways:
- Additive changes ship at any time: new fields, new error codes, and new values in open lists such as
providerslugs. Ignore fields and values you do not know rather than rejecting the response. - Removals, renames and changes of meaning are announced at least 90 days ahead, in the changelog and by email to every account that holds an API key.