Skip to main content
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.
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.
  • 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.
  • 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.