Skip to main content
These examples assume PERIPHERY_API_KEY is already set in your environment, and use curl and jq. See Authentication for key creation and headers. The one-liners pass --retry 3 --retry-delay 2 --retry-max-time 30, so curl retries a 429, 502, 503 or 504 a few times, waiting at least as long as Retry-After asks. A retried monthly 429 costs nothing, but a retried 504 uses another search each time, as can a retried 502 or 503 from a search; see which requests use a search. The scripts use the helper below instead, which retries only what is worth retrying.

A request helper for scripts

api_get sends one GET and prints the JSON body on success. It retries rate limits (429 rate_limited and 429 ip_rate_limited), 502 and 503 busy, up to five attempts, waiting for Retry-After or a doubling backoff, whichever is longer. It also retries when the connection fails. Anything else, such as a 504, a 4xx or the monthly 429, fails at once and prints the JSON error to stderr, and so does a missing PERIPHERY_API_KEY. Save it next to your scripts:
periphery-api.sh

Page through every result

Search returns 50 hits per page. This script prints every matching hostname once. It stops when hasMore is false, and waits between requests to stay within the one-request-per-second limit. Pages are offsets over live data, so an app can show up on two pages: the final awk keeps each hostname once.
all-hosts.sh
Every page is one search against your monthly allowance: a query with 40,000 matches takes 800 searches to read in full. Narrow the query first when you can.

Fetch the full record for one hit

Search hits carry the snapshot links (favicon, screenshot), email addresses and the names of the detectors that verified a credential, but not the extracted text or the credential findings. Use the hostname as the stable identifier when you need those. Fetching a record uses no search.
A 404 with the code app_not_found means search cannot return that hostname: Periphery has never visited it, has marked it dead, or the hostname is not valid. faviconUrl and screenshotUrl expire an hour after the response that carried them. To download an artifact later, fetch the app record again for fresh links, rather than running the search again. This saves the screenshot of one app:
save-screenshot.sh
Each record fetch shares the one-request-per-second limit, so space them a second apart when refreshing many apps.

What’s new for my org

firstSeen: keeps apps Periphery saw for the first time within the window, and search hits carry firstSeen. This script is meant to run once a day. Its window, two days, is twice the interval between runs, so a late or failed run misses nothing; the file of hostnames already reported keeps each app from being reported twice.
whats-new.sh
Each run uses one search per page of results, usually one: about 30 searches a month when run daily.

Find apps with verified credential findings

The has:creds filter keeps apps with at least one verified credential finding. verifiedDetectors names the detectors that verified one.
Use the app endpoint only for records you actually need to inspect. Its credentials array lists each finding, masked.

Keep credential-bearing responses out of logs

API responses can contain email addresses, masked credential values and, through text and screenshotUrl, what the page displayed when it was captured, including any secret it printed on screen. A signed link also works for anyone who holds it until it expires. Avoid dumping complete responses into CI logs, chat systems, tickets, analytics, or third-party AI tools unless your organisation has explicitly approved that data flow. The same applies to the MCP server: what an assistant reads goes to its AI provider. Select only the fields the next system needs. For example:
This keeps emails, credentials, text and the signed links out of the output.

Retry the errors that are retryable

Switch on the code in the error body:
  • rate_limited and ip_rate_limited (429): wait for Retry-After, one second, then retry.
  • monthly_limit_reached (429): stop searching until 00:00 UTC on the 1st. Retrying will not help.
  • unavailable (502) and busy (503): retry after Retry-After, backing off if it keeps failing.
  • maintenance (503): retry after Retry-After, an hour.
  • timeout (504): narrow the query (more words or a filter) instead of retrying it unchanged. A retry within a minute returns the same 504 and uses another search.
  • query_required, query_too_long, too_many_terms and page_out_of_range (400): fix the query or the page.
  • api_key_missing, api_key_invalid (401) and plan_required (403): fix the key or plan instead of retrying.
  • https_required (403): call https://, and rotate the key you sent over plain HTTP.
  • app_not_found (404): treat the record as unavailable rather than repeatedly polling it.
Never treat an error as an empty result. See Limits and errors for the complete error table.