Start with a question
Search by a software name or describe the task you need help with. Narrow the candidates, open a record and check its sources before deciding.
- People: use Search above to find candidates, apply filters and open their details.
- Agents: use the documented search and record interfaces below. JSON and text results carry the same recorded evidence and its limits.
- No account or API key is needed for public reads within the published limits. Missing evidence stays unknown; a match does not prove that software can complete your task.
What this is
A documented, keyless HTTP interface over the PulseGate index. There is no registration, no API key, and no account: the endpoints below answer public requests — GET, and POST for the protocol endpoint — within published limits.
We document a deliberately small part of what the service answers. An endpoint on this page has a shape we will not change without notice. An endpoint that is not on this page may answer today and stop tomorrow, and that is the documented behaviour.
What this is not: a review source, an endorsement, a ranking, a scoring service, or investment or legal advice. The index records what public sources say and where we read it. It does not rate anything.
Find candidates, then read the evidence
Some apps, recent changes or details about what an app can do may be missing from the index. No matches in the indexed data does not mean no suitable service exists.
In a browser, use /search?q=PDF and follow a candidate to /apps/{slug}. The GET form, filters, pagination and record links work without JavaScript. The page offers JSON and text alternatives and a link back to this guide.
For a direct HTTP client, ask GET /api/search?q=PDF&format=json or GET /api/search?q=PDF&format=text. For a single record, use GET /api/apps/{slug}?format=text. Both representations use the same public fields, limits and visibility rules. JSON is the default. Without format, Accept negotiates application/json or text/plain; unsupported Accept returns 406. Unsupported or conflicting format returns 400. Explicit format wins over Accept. HEAD returns GET status and headers without a body and may still do the GET work.
A successful search has at most ten candidate identities across lead, results and nearest, and at most 32 KiB per representation. Nearest candidates are labelled. These are candidates to inspect, not endorsements or proof that a requirement is met. Follow recordUrl to inspect the profile and sources.
Read each profile claim together with value, basis, sourceRefs and observedAt. Inferred classifications are not functional tests. Null means unknown, not false. Record-level sources identify a listing; they do not establish every claim. recordChangedAt dates our record, not verification of all claims. Profiles are at most 16 KiB inside a response of at most 32 KiB.
A successful empty result means this response has no candidates in the requested evidence and paging scope. With unknown=exclude, evidenceLimited can mean the index cannot establish the requested facts; a later empty page can mean the end of the accessible window. Neither proves that no such software exists. HTTP 400 means repair the input, 403 means refused depth, 406 means choose a supported representation, 404 on a record means unavailable in the public catalogue, and 429 means wait for Retry-After. HTTP 503 means temporarily unavailable, not zero matches; wait at least Retry-After (five seconds on the search/detail handler) before a bounded retry. Do not combine an error with stale candidates.
On HTML, inspect the completed result section or error alert. A streamed source failure may arrive with HTTP 200 and a visible Search is temporarily unavailable alert. A navigation or prefetch envelope without either section establishes neither success nor an empty result.
Declared headless clients can use this bounded search and individual-record workflow through the same admission checks as other clients. Use an honest User-Agent. Direct client access and permission to crawl are different: crawlers must follow their robots.txt group. This guide does not authorize catalogue harvesting or redistribution.
Three small tasks to try and revisit
Find open-source PDF software: open /search?q=PDF&require=license:open-source&unknown=include, read the attributed evidence on a candidate, and check missing facts at the linked source. For a task requiring an API, try /search?q=PDF&require=api:true&unknown=exclude; a limited-evidence result is useful information, not proof that no suitable project exists.
Save a search: copy the search page’s Save this search address link into your own notes or bookmarks. Reopening it runs the same question against the current index, so the candidates may change. The query and filters are in the address; avoid personal information and secrets. Saving the URL and using search need no JavaScript or account.
Follow a few chosen projects: use their Watch controls and open /watchlist. This convenience needs JavaScript and stores at most 25 slugs plus a comparison snapshot in this browser. Clearing browser data clears that snapshot. The slugs are sent when checking the list or opening its RSS address; a shared address reveals the chosen list.
Read the list in an RSS reader: /feed/watchlist.xml?w=slug-one,slug-two works without JavaScript or an account. It reports at most 25 dated events from the last 60 days: the latest recorded GitHub release and evidence-backed confirmed disappearance. It does not report every change, continuously monitor your needs, or notify by email. Renamed listings are resolved to their current public record; hidden and excluded records stay out. An empty feed means no eligible dated event in that window, not no change anywhere.
For automated return visits, read the same feed at most once every six hours with concurrency one; this is a recommendation, not a capacity entitlement. Respect 429 and Retry-After without switching identities. The watch feed accepts at most 4096 UTF-8 bytes in one w value, reads the first 25 distinct valid slugs and says how many were omitted. The complete XML is at most 32 KiB. GET and HEAD use the same read checks and charge 100 catalogue units; HEAD may perform GET work. A record-read or size failure is 503 with Retry-After: 5 and no events. If only rename-history lookup fails, an old slug may yield no event; this does not establish disappearance. Requests remain subject to the shared quota; this does not open other feed or API paths.
Endpoints
Three tiers, and the difference matters. The agent-facing tier is open in robots.txt to the crawlers of OpenAI, Anthropic and Perplexity; exact /search and /api/search reads also open to general crawlers except Common Crawl; the client-facing one is excluded from crawling; the protocol endpoint serves agents acting on a request and is never crawled.
- Agent-facing, open to the OpenAI, Anthropic and Perplexity crawlers — GET /api/search · GET /api/apps · GET /api/apps/facets · GET /api/categories · GET /api/stats/public · GET /api/industry-updates
- Client-facing, excluded from crawling in robots.txt — GET /api/apps/{slug}
- Agent protocol (Model Context Protocol), POST only — POST /api/mcp
/api/apps/{slug} is documented for programs that call it directly. It is uncached, costs a database read per request, and returns fields that are already on the crawlable HTML record page, so an agent that honours robots.txt should read the record page instead. This is deliberate, not an oversight.
The same endpoints are described for programs in OpenAPI 3.1 at https://www.pulsegate.ai/openapi.json; where that file and this page differ, this page applies.
GET /api/search — ask a question
Parameters: q (at most 120 Unicode code points, 512 UTF-8 bytes and 12 words; excess returns 400); page (1-50); limit (1-10); platform, license, pricing and category as recorded filters. require accepts up to three api/mcp/cli/selfHosted:true|false, pricing:<value> or license:<family> conditions, comma-separated; at most five recorded filters and requirements combined. unknown=include keeps candidates needing verification; unknown=exclude requires attributed evidence. Unsupported conditions return 400.
Successful JSON carries shape, query, lead, results, nearest, relaxedBy, axes, scope and tookMs. Check HTTP status first: an error envelope has no usable count or candidates. The text form distinguishes success, nearest candidates and errors.
- The result list is capped at 10 rows. A limit above that is accepted and ignored, and the response does not echo it back — so do not infer page size from your request.
- scope.matched tells you how many rows matched; scope.available how many you can reach.
- Every row carries recordUrl, the permanent address of our record of it — the link to cite. domain is the software's, not ours.
- Responses are private and no-store. Internal bounded result caching still rechecks public visibility. There is no published latency guarantee.
GET /api/industry-updates — a bounded Wire list
Read /wire for HTML or /api/industry-updates for JSON. GET and HEAD admit declared agents for these exact list paths only. Supported parameters are view=today|front|week|ranked|river (default today — the day's stories, then the week's earlier key stories, each item marked with its section, today or week; front is the front page; week is the week's key stories; ranked and river list every published item), timeframe=24h|7d|30d (default 7d), cat, sub, leaf, kind and provider. JSON also accepts limit from 1 to 50, default 20. No event, saved view, cursor, offset or custom date range; story details, widgets and subpaths are outside this permission. Duplicate or unknown parameters are outside the agent workflow.
The HTML first list has at most 24 items when unfiltered and 20 when filtered. The same bounded item projection supplies HTML and JSON. Each serialized item is at most 3,276 UTF-8 bytes; the complete JSON response is at most floor(32,768 × limit / 10) bytes. These are enforced operational ceilings, not measured Wire byte quantiles; the HTML ceiling applies to its list data, not the complete document. Whole items form a prefix: byteLimited and hasMore explain a shorter response, and nextCursor is always null. URLs are never truncated to fit. A first item or metadata that cannot fit makes the source unavailable.
A refused category scope returns an explicitly empty list; it never widens the request. Internal cache revalidates after 60 seconds and may serve a successful snapshot for at most 180 seconds from acquisition. A still-valid snapshot need not reveal a current source outage. Expired, invalid or future acquisition times are refused; a cache hit or failed refresh never renews that clock. Without a valid snapshot, source or size failure returns API 503 with Retry-After: 5 and no list. HTML shows The wire did not answer; streaming may keep HTTP 200, so inspect the rendered answer. Respect 429 and Retry-After, pause polling while hidden, and use concurrency one with bounded retries. Outer responses are private and no-store. A cached or empty answer still uses the shared admission checks.
timeIsOurs means the displayed time is our detection time, not the publisher's timestamp. lastSuccessfulRunAt is the last publication time, not a freshness or delivery SLA. Wire crawling is excluded for training crawlers, including GPTBot and ClaudeBot. The list offers metadata, bounded summaries and source URLs; check the source for its full account and rights.
GET /api/apps — page the catalogue
Filters are documented in the response itself: /api/apps/facets returns every facet and its counts, /api/categories the category tree.
Three behaviours you must know before you build on it:
- An unknown parameter is ignored silently. A typo does not produce an error, it produces different data — and /api/apps/facets cannot catch it: it names each facet by its field, which is not always the name of the parameter /api/apps reads.
- Applying any filter changes the default ordering from balanced to firstSeen. Read meta.order; do not assume it.
- items and apps are the same array. Read items; apps is legacy and may go.
- Every item carries recordUrl, our record's permanent address, and firstSeenBasis beside firstSeenAt, and meta.methods names the methods behind the items' fields. All three are defined under What the fields mean.
Responses are private and no-store. Before cached list items leave the service, current public visibility is checked; removed items are omitted without replacement. Aggregate counts can describe the cached snapshot. Depth: page is at most 50 and the offset at most 5,000, whichever binds first. Beyond that the response is 403 with {"error":"pagination_limit"}. limit is clamped to 100.
GET /api/apps/{slug} — one record
Client-facing tier, see above. Returns the public fields of a single record.
A record outside the catalogue is absent, not hidden inside the payload: deleted, taken down by an administrator, unlisted, not eligible, excluded by curation, or folded into another record as a duplicate, it answers 404 — the same as a slug this index does not hold. A record confirmed gone is still in the catalogue, still answers, and says so in its own fields.
A renamed record keeps its former slug: a request for it answers 308, with Location naming the endpoint at the current slug. A slug held today by a record in the catalogue answers for that record, never with a redirect. The record carries recordUrl, firstSeenAt and firstSeenBasis, defined below.
POST /api/mcp — the Model Context Protocol endpoint
For assistants and agents that speak MCP. Point a client that supports the Streamable HTTP transport at https://www.pulsegate.ai/api/mcp — no key and no account. It is stateless JSON-RPC 2.0 over POST: initialize, then list and call its three instruments. A GET answers 405, because there is no event stream to open.
search_catalog accepts the same q, filters, requirements, unknown, page and limit as search, with integer page/limit and no extra properties. Its text and structuredContent describe one shared search. A full profile instrument is not available; use the record page or documented record endpoint.
HTTP 200 application/json means a JSON-RPC response was delivered. Inspect result.isError: search failures carry structuredContent.error.status (400, 403 or 503), a code and a message; 503 includes retryAfterSeconds: 5. Empty successful search has isError: false. Transport-level 400/403/413/415/429 and their Content-Type are separate from instrument results. No sessions, batches or event stream.
lookup_project returns the external identifiers PulseGate holds for one listing — found by its slug, or by a platform and an identifier you already have — and, for each one, how it arrived and whether anyone has checked that it belongs. list_identifier_platforms lists the platforms identifiers are recorded on, with how many each holds.
- It says nothing about whether software is alive or maintained. Every provenance answer carries what_this_answer_does_not_say; keep it with anything you quote. For liveness, read aliveStatus from /api/apps; /api/search does not carry it.
- url in its answer is the software's own address. record_url is the permanent address of our record of it. A renamed listing answers by its former slug too, and then says renamed_from: the slug you asked by, beside slug, its current one.
- A listing that holds more than 25 identifiers on one platform carries a known identity-collapse defect in our data. Reached through an identifier, it is refused rather than attributed; reached by its slug, it answers without those platforms and names them.
- It answers for the same records /api/apps/{slug} does; a record outside the catalogue is absent, the same as one this index does not hold.
GET /api/stats/public — the published figures
Index size, recent indexing volume, and a seven-day daily series. Every figure carries the window it covers: a bare count with no window and no date is not a claim we stand behind, because the index changes daily.
Limits
Per minute, per IP — this is the one you will meet. The default count covers the catalogue endpoints below in that minute, and each request is checked against the ceiling of the endpoint it asks: sixty requests to /api/apps leave none for /api/stats/public in the same minute. An IPv6 address counts by its /64.
The ceilings:
- 80 per minute — /api/apps, /api/apps/facets, /api/search
- 60 per minute — /api/categories, /api/stats/public, /api/mcp
- 600 per minute — the existing Wire read tier, including /wire and /api/industry-updates. Catalogue images have a separate 60-per-minute counter; image exhaustion does not spend the default catalogue minute counter. These separate minute counters do not create new daily or shared data allowances.
Per day, per IP, there is one more count, and each request is checked against the ceiling of the endpoint it asks: 20,000 for /api/apps and the paths beneath it, 5,000 for /api/search. Every request to /api/search adds to it; a request to /api/apps or beneath it adds only once the IP has gone over roughly 30 requests in its current minute, on any endpoint, and so do record pages and some other requests to this site, under the same rule. Every search therefore counts toward the 20,000, and a burst on /api/apps spends the 5,000 of /api/search as well.
/api/mcp has its own: 2,000 calls per day, per IP, counted from the first call.
One additional catalogue-data allowance spans HTML, JSON, text and MCP on the same network identity. An anonymous network has at most 500,000 release units per UTC day, with additional shared/global ceilings. Search reserves 80 units, a JSON/text record 16, an HTML record 136 and every MCP POST 80, including initialization, metadata and errors. Wire reserves 16 units per requested list item: JSON 320 by default and at most 800; HTML 384 unfiltered or 320 filtered. HEAD on search, individual records and Wire reserves the same weight as GET because it executes that work; HEAD on other data families retains its existing policy. Failed and duplicate requests are not refunded. Switching format, instruments, tabs or User-Agent does not create another allowance; shared networks share limits.
Protected catalogue responses carry X-PulseGate-Data-Policy, X-PulseGate-Data-Mode, X-Catalog-Units-Limit and X-Catalog-Units-Remaining when admitted. Each request is checked before any internal result-cache read; externally these responses are no-store. If the distributed budget is unavailable, a conservative per-instance degraded ceiling of 5,000 units per hour applies; this is not a distributed guarantee.
Responses reaching the legacy per-minute count carry X-RateLimit-Limit and X-RateLimit-Remaining, and usually X-RateLimit-Reset. A 429 adds Retry-After; a shared catalogue refusal can happen earlier and omit these legacy headers. Retry only after the stated wait, with bounded retries.
Outside the bounded search, individual-record, Wire list, protocol and discovery workflow, a declared headless browser or scraping framework can be refused with HTTP 403 plain text before admission. The bounded workflow still has admission and rate checks. HTTP success never grants permission to ignore robots.txt or the Terms.
Do not work around a limit. Access within the published limits is permitted; access that exceeds or circumvents them is not, and section 7 of the Terms applies to it.
What the fields mean — and what they do not
We would rather be dull and correct than confident and wrong. These fields need saying plainly.
aliveStatus is one of alive, gone, or unverified:
- gone means a liveness check ran and found the thing gone.
- alive means a liveness check ran and found it there.
- unverified means we have not established it — not "probably alive". It is a different kind of statement, not a weaker one. A record we fetched successfully but never assessed is unverified, because a 200 is the absence of bad news and not a positive finding.
lastCheckedAt is the time of the liveness check. If no check has run, it is null. It is never filled in from some other date we happen to hold.
firstSeenAt is when this index first saw the record — not when the software was released. One exception, stated with its number so nobody reads past it: for records indexed before October 1, 2026 it is often an estimate placed shortly after the record's documented release, by a historical backfill we have since stopped. Measured on October 2, 2026, firstSeenAt precedes the record's own creation in this index by more than a day for 178,995 of 354,118 records, which is 50.5 per cent, and the earliest such value is January 1, 2022. Treat it as at best a lower bound on the project's age for anything older than October 2026, not as an observation. Where we have a documented release date it is a separate field with its own provenance.
firstSeenBasis says, record by record, how firstSeenAt was set. observed: this index saw the record at that time. backfilled: a backfill's own record shows it wrote the date, or the date is older than this index, so firstSeenAt is not an observation — read it as at best a lower bound on the project's age. unknown: we hold a date and could not establish which of the two it is — read it as you would backfilled. null: there is no first-seen date. It is on /api/apps items and on /api/apps/{slug}, and it describes our date, not when the software was released.
recordUrl is the permanent address of our record — the page to cite. It is not the software's address: canonicalUrl and outboundUrl point at the software. It is on /api/apps items, /api/search rows and /api/apps/{slug}; MCP search_catalog keeps recordUrl, while lookup_project names it record_url.
meta.methods, on /api/apps, names the methods that decided fields of the items in that response, each as an id and the version it was computed with: liveness decides aliveStatus and deadConfirmedAt, and first-seen-basis decides firstSeenBasis. A method is named only when the items carry a field it decides, so a response with no items names none. Each method's definition and changelog are on /methodology; quote the version with anything you build on those fields.
Stability
Fields on this page will not be removed without notice. Endpoints not on this page carry no such promise: if you build on something undocumented and it changes, that is the documented behaviour.
Terms
Use of the endpoints on this page, within the published limits, is expressly permitted under section 7.1 of our Terms. That permission covers access; it does not by itself grant redistribution of substantial portions of the index.
This page applies as published from time to time. Effective date: October 7, 2026. A change to the endpoints, their limits, or this permission is not retroactive: access that complied with the permission in effect at the time of access does not become a violation because this page later changed. Previous versions are kept and available on request. Versions: October 1, 2026 — first publication; October 2, 2026 — POST /api/mcp added to the endpoints this permission covers; October 3, 2026 — recordUrl, firstSeenBasis and meta.methods documented, former slugs of renamed records answer, the endpoints described in OpenAPI 3.1, and the limits restated as they are enforced; October 6, 2026 — bounded HTML/JSON/text reading, search_catalog and watchlist RSS, explicit uncertainty and shared catalogue limits; October 7, 2026 — bounded Wire list access, shared capacity pricing and current-visibility checks on cached catalogue lists.