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 whenhasMore 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
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.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.
Refresh expired links
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
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
Find apps with verified credential findings
Thehas:creds filter keeps apps with at least one verified credential finding. verifiedDetectors names the detectors that verified one.
credentials array lists each finding, masked.
Keep credential-bearing responses out of logs
API responses can contain email addresses, masked credential values and, throughtext 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:
emails, credentials, text and the signed links out of the output.
Retry the errors that are retryable
Switch on thecode in the error body:
rate_limitedandip_rate_limited(429): wait forRetry-After, one second, then retry.monthly_limit_reached(429): stop searching until 00:00 UTC on the 1st. Retrying will not help.unavailable(502) andbusy(503): retry afterRetry-After, backing off if it keeps failing.maintenance(503): retry afterRetry-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 same504and uses another search.query_required,query_too_long,too_many_termsandpage_out_of_range(400): fix the query or the page.api_key_missing,api_key_invalid(401) andplan_required(403): fix the key or plan instead of retrying.https_required(403): callhttps://, and rotate the key you sent over plain HTTP.app_not_found(404): treat the record as unavailable rather than repeatedly polling it.