API reference
Plain REST over HTTPS with JSON responses. Tender search, contract awards, buyer summaries and signals are free GETs with no key. The checkers are free and rate-limited under a weekly cap (the live caps are at GET /v1/pricing). All endpoints live under https://www.stipple.sh.
Conventions
- Document inputs are accepted three ways: multipart
file, JSON{"bytes_b64": "...", "filename": "..."}, or JSON{"url": "https://..."}(public hosts only; fetched server-side with strict SSRF protections; analysis endpoints only). - Long-running calls support
?stream=1— a Server-Sent Events stream (received→workingheartbeats →result) so proxies never time out mid-analysis. - Identical bytes are cached by content hash — re-submitting the same document is free and instant.
- Errors use standard status codes with a JSON
detailmessage — see Errors & limits. - OpenAPI: the full machine-readable contract is published at
https://www.stipple.sh/openapi.json— generate a typed client or import it into your tooling. - Sandbox: add
?sandbox=true(or the headerStp-Sandbox: true) to any tool for a deterministic, clearly-labelled fixture response ("sandbox": true) — free, and it never counts against your weekly limit. - Idempotency: send an
Idempotency-Keyheader on a POST so a retry replays the stored response instead of processing twice (a reused key with a different body is a422). - Rate-limit headers: every response carries
RateLimit-Limit/RateLimit-Remaining/RateLimit-Resetso a client can self-throttle.
Search open tenders
GET /v1/tenders
Free, no key. Open tenders across Australia; New Zealand rows only when you ask for them (jurisdiction=NZ). Federal, state, council, university and health. Grants ride the same search with kind=grant. Returns {total, results[], coverage}. The link to the original portal (link, url, listing_url, source_url) comes only with an API key or a signed-in session.
Read coverage. It names which sources were searched and which returned nothing, so an empty result means “no match in what we searched” — never “no such tender exists”. There is deliberately no location filter: it is populated on a small minority of rows against jurisdiction's every row, so filtering by it would silently hide most of the corpus.
| Parameter | Type | Description |
|---|---|---|
| q | query | Free-text search across title, summary and buyer. |
| jurisdiction | query | A country (AU, NZ) or a subdivision: AU-NSW, AU-VIC, AU-QLD, AU-WA, AU-SA, AU-TAS, AU-ACT, AU-NT. |
| kind | query | grant for grant rounds from every grant source, tender for everything else; leave it out for both. |
| tier, source, category | query | Filter by government tier, the source registry id, or category. |
| buyer | query | The organisation name as published, matched exactly (case-insensitive). |
| closing_before | query | ISO date — tenders closing on or before this day. |
| closing_within_days | query | 0 to 366 — closing between today and today plus N, in the buyer’s own day. 0 is today. A tender with no closing date never matches. |
| changed_within_days | query | 1 to 31 — a closing or published date changed in the last N days. |
| first_seen_after | query | ISO-8601 instant, strictly newer — what is new since your last look. |
| include_closed | query | Default false. Open tenders only unless you ask otherwise. |
| limit, offset | query | Paging. |
curl "https://www.stipple.sh/v1/tenders?jurisdiction=AU-QLD&q=road&limit=2"One result, as captured from the live API on 15 September 2026:
{
"total": 15,
"results": [
{
"title": "RFT260091 - Capella Rubyvale Road Upgrade CH 47.3 - 48.6",
"buyer": "CENTRAL HIGHLANDS REGIONAL COUNCIL",
"jurisdiction": "AU-QLD",
"tier": "council",
"closing_date": "2026-09-21",
"source_tag": "VendorPanel",
"link": "https://www.vendorpanel.com.au/PublicTenderAccess.aspx?id=...",
"tender_key": "17d2cc3cca27d22ef5874d9be40ab210"
}
],
"coverage": {
"sources_total": 23,
"sources_empty": [ { "source": "sa", "status": "blocked", "why": "login wall: ..." }, ... ],
"last_run": "2026-09-14T12:32:04+00:00"
}
}One tender, open or closed
GET /v1/tenders/{key}
Free, no key. One tender by the page_key every search result carries (32 hex, or f + 32 hex for a tender that publishes no reference or buyer), in the same shape as a search result plus open — the search's own rule: no closing date, or a closing date of today or later in the buyer's time zone, taken from its state (Sydney for a tender labelled with the country alone). A closed tender still answers. An unknown key is a 404. An Australian tender's public page is /tenders/au/<slug>-<page_key>.
The link to a tender's original portal
GET /v1/tenders/{key}/portal
An API key or a signed-in session. The link to the tender's or grant's original portal: {url, portal_name, link_is_listing}. link_is_listing is true when the portal has no page per tender and the link opens the list it appeared on. 401 without a key or a session; 404 for an unknown key or a tender we hold no link for.
What a tender's page shows beside it
GET /v1/tenders/{key}/context
Free, no key. One read for everything around a tender: buyer (the buyer's nightly summary, its five most recent contract awards and five contracts ending soonest), similar (keywords chosen from the tender's title, the contract awards whose titles carry them with their count and published value range, and up to five open tenders like it, each with its page_key) and sources (attributions, computed_at and the award window). Either block is null when nothing is held.
The keywords are a heuristic, and the payload says so (match: {method: "heuristic", rule: "title_keyword"}): the rarest title word, matched as a whole word, that at least three contract awards carry, plus a second word only when the pair narrows the match. Supplier names are shown to a signed-in caller only, as on the contract award routes; each award says whether one is held (supplier_held). An unknown key is a 404; while the intelligence store loads the answer is 503.
Counts for the search page's quick views
GET /v1/tenders/strip
Free, no key. Three counts of open tenders: closing_48h (closing today or tomorrow), dates_changed_7d (a closing or published date changed in the last 7 days) and new_7d (first seen in the last 7 days). Each equals the total of the matching search (closing_within_days=1, changed_within_days=7, first_seen_after).
Where the tender data comes from
GET /v1/tenders/sources
Free. Every nightly and observed snapshot source, including its compact tag, full name, refresh mode, tender count, and most recent run or snapshot date; its URL with an API key or a signed-in session. This is the honesty surface — it names the sources behind login walls and the ones that returned nothing, which is usually the answer to “why did my search come back empty?”
Rank tenders against what a company does
POST /v1/tenders/match
Reads a company's website, builds a capability profile, and ranks open tenders against it. Free. Each match carries why[] — the company's own stated capabilities the tender needs — and gaps[], what the tender asks for that their site does not mention.
score is relative fit within these results, not a probability of winning. degraded: true means scoring was unavailable and the order is keyword relevance only, with no why/gaps — present it as unjudged rather than as ranked. An unauthenticated call returns the strongest few and counts the rest in withheld; shown + withheld == matched always holds.
| Parameter | Type | Description |
|---|---|---|
| url | body | A company website. A bare domain is fine — myrisk.io resolves. |
| example | body | Instead of a url: civil, it or facilities. Skips the site read. |
| jurisdiction, closing_before | body | Optional filters, same values as the search endpoint. |
Signed in, POST /v1/tenders/watch saves any of these as a daily email: a search, a company profile, a buyer, a signal filter or a grants search — five of each kind, one email a day at most, nothing when nothing is new.
Contract awards and buyer intelligence
Awards as published — AusTender (Department of Finance, CC BY 3.0 AU) and the Queensland Government's contract disclosure reports (CC BY 4.0) — rolled up nightly by buyer, with the contracts ending within 12 months and buyers' planned procurements. Every payload carries computed_at, the night its figures are true for, and sources with the attribution each licence requires. Values are the published amount and currency, never converted.
withheld counts the records affected and withheld_reason carries the sentence to show. The same rule applies on the MCP tools. Until the store has loaded after a deploy, these routes answer 503 {"ready": false} — retry in a moment.Contract awards
GET /v1/awards
| Parameter | Type | Description |
|---|---|---|
| buyer, buyer_key | query | The buyer as published (exact), or its key from a previous answer. |
| supplier, supplier_key | query | The supplier as published, or its key (AU-ABN:<abn>). |
| q | query | Free-text search across title, description, buyer and supplier. |
| jurisdiction | query | A country matches everything in it; a subdivision matches itself. |
| since | query | ISO date — awarded on or after this day. |
| limit, offset | query | Paging; limit up to 100. |
Returns {total, results[], computed_at, sources, withheld, withheld_reason}. Each award carries contract_id, buyer, buyer_key, title, value_amount, value_currency, awarded_date, start_date, end_date, categories (UNSPSC), and — signed in or with a key — url, supplier, supplier_id and supplier_key.
The buyer cohort
GET /v1/awards/buyers
| Parameter | Type | Description |
|---|---|---|
| country | query | Default AU. |
| order | query | count (default) or value — rank by award_count or by value_total. |
Every buyer with a public page for a country: at least 20 contract awards in the window or at least 5 open tenders, recomputed nightly. Returns {country, total, buyers[{buyer_key, name, slug, jurisdiction, award_count, open_tenders, expiring_12m, planned_12m, median_value, value_total, value_currency}], computed_at, sources}. A buyer page lives at /buyers/au/<slug>-<buyer_key>.
Buyers new to the corpus
GET /v1/awards/buyers/new
| Parameter | Type | Description |
|---|---|---|
| since | query | ISO date. Required. |
| country | query | Default AU. |
How many buyers were first seen in a contract award on or after since. Returns {since, count, computed_at}.
A buyer by the name a tender publishes
GET /v1/awards/buyers/lookup
| Parameter | Type | Description |
|---|---|---|
| name | query | The organisation name as published — what a tender’s buyer field holds. |
| jurisdiction | query | The tender’s jurisdiction. Resolves under it first, then the federal key of the same country; never across states. |
| source | query | The tender’s source_id. A source that files every tender under the country alone never resolves to a national buyer. |
Returns the same payload as the buyer route below, or 404 when nothing is held for that name. When the exact name misses, two labelled heuristics are tried: the name without a trailing division code, and, for a tender labelled with the country alone, the one state the name names. Their result adds match, which names the rule used.
curl "https://www.stipple.sh/v1/awards/buyers/lookup?name=Transport%20for%20NSW&jurisdiction=AU-NSW"One buyer: the summary and what is behind it
GET /v1/awards/buyers/{buyer_key}
{buyer, expiring[], planned[], recent_awards[], top_suppliers[], open_tenders[], computed_at, sources}. The buyer object is the nightly summary: award_count and window_from (the start of the awards window the counts cover), median_value, p25_value, p75_value, value_currency, median_response_days (publication to close across its tenders), expiring_12m and planned_12m. top_suppliers is a name and a share, never a page. Buyers are one row per published spelling — no alias merging.
The entity behind an identifier
GET /v1/awards/suppliers/{identifier}
An ABN (digits only or spaced) or a scheme:id key. Returns {entity, contracts[], buyers[], categories[], standing, computed_at, sources}: the names seen, the registry number and standing, the contracts held, the buyers won from and the categories. The website's supplier page is for signed-in people only; this route is the same entity. Anonymous callers get the counts with the names and identifiers withheld.
What may be tendered before it is
GET /v1/signals
| Parameter | Type | Description |
|---|---|---|
| kind | query | contract_expiry (a contract ending, with its incumbent), planned_procurement (a buyer’s stated plan with its quarter and spend band as published), or recurring_tender (derived from our own history). |
| jurisdiction, buyer, q | query | Filters; q searches the subject, buyer and incumbent. |
| window_before | query | ISO date — signals whose window starts on or before it. |
| window_after | query | ISO date — signals whose window has not ended by it; a window with no end date is kept. |
| first_seen_after | query | ISO-8601 instant — signals first seen after it. |
| limit, offset | query | Paging. |
Each signal carries confidence — published or derived, a vocabulary, not a score — window_start and window_end (never invented: an expiry's window is the contract's end date, and a planned row with no parseable quarter has none), value_text, evidence_ref, status (the source's own word for a plan, for example As Published or To Market; empty where the source gives none), released (true when the plan is marked To Market), and — signed in or with a key — evidence_url and incumbent. A plan its agency withdrew is never returned.
curl "https://www.stipple.sh/v1/signals?kind=contract_expiry&jurisdiction=AU-QLD&limit=5"Planned procurements, with counts by jurisdiction
GET /v1/intelligence/planned
The signals route filtered to planned_procurement, plus by_jurisdiction counts for building chips. Same filters and paging, and buyer_key, quarter (an id like 2026Q4 from by_quarter, later or ended; a well-formed id outside the current five answers an empty list), window_after (YYYY-MM-DD; a window with no end date is kept), sort (soon or buyer) and exact=1 (the jurisdiction code alone, not its states); a bad value returns 400. A withdrawn plan is never returned, and each row carries status, the agency's own word, and released. The facets ignore the row filters: figures, by_quarter, top_buyers and top_categories.
One planned procurement, in context
GET /v1/intelligence/planned/{plan_id}/context
The plan's buyer summary and that buyer's own contract awards for similar work, inside the summary's window: a count and how many end within 12 months, matched by words in the title (a heuristic, labelled by match). Counts only, no names.
The live block for a state or country
GET /v1/intelligence/{jurisdiction}
What the state pages render: {jurisdiction, name, expiring{total, federal_by_region, horizon_days, rows[]}, planned{total, quarter, rows[]}, buyers[], open_tenders, region_basis, computed_at, sources}. Commonwealth contracts are attributed to a state by the buyer's published office region, and region_basis says so.
The front page's one payload
GET /v1/intelligence/summary
{open_now, new_this_week, expiring_12m, planned_quarter, buyers_month, grants_open, sources_total, countries, by_jurisdiction, previews, computed_at}. Always the anonymous rung — counts and previews, no names.
Every trade and state, with its market page's figures
GET /v1/intelligence/trades
{pairs[{trade, jurisdiction, open_now, ending_12m, planned_quarter, year_awards, volume, eligible}], minimum, year, match, computed_at}, largest first. A trade is a labelled heuristic: whole words of a row's title and published category, and category code families. Counts only.
One trade in one state: what is coming up
GET /v1/intelligence/trades/{trade}/{state}
{open, ending{days_90, covers_90, days_365, by_month[{month, count, rows[]}], chart, rows[], calendar}, planned, recurring, buyers{rows[], by_value[]}, concentration, eligible, computed_at}. Every figure carries covers, the jurisdictions its rows came from. The open tenders are /v1/tenders?trade=&jurisdiction=&kind=tender. Supplier and holder names, and the re-tender calendar, with a key or a signed-in session.
Open grant rounds
GET /v1/grants/summary
From the tender corpus alone (GrantConnect): {open, closing_month, month_end, value_open, value_currency, with_value, by_jurisdiction, closing_soon[], computed_at, sources}. value_open sums only the rounds that state a value, and with_value says how many did. Each closing round carries its value, eligibility and GrantConnect's own type as published.
Fact-check a document
POST /v1/verify-references
Verifies a document's citations (live / archived / dead, scholarly papers matched against their claimed titles), recomputes its internal arithmetic, and — in deep mode — fetches each cited source to judge whether it supports the claim. Results persist under a content-hash id and include a public shareable permalink.
| Parameter | Type | Description |
|---|---|---|
| file | text | bytes_b64 | url | body | The document: multipart file, raw prose, inline base64, or a public URL. |
| deep | query, bool | Also fetch each cited source and judge support/contradiction (slower, ~a minute). |
| stream | query, bool | Return an SSE lifecycle stream instead of a single response. |
curl -X POST "https://www.stipple.sh/v1/verify-references?deep=1&stream=1" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/report.pdf"}'Key response fields: summary (totals + headline), references[], claims[], internal_consistency[], limitations, check_id, permalink.
Retrieve a fact-check
GET /v1/fact-checks/{check_id}
Fetch a stored fact-check result by its content-hash id (fc_ + 16 hex). This is the data behind the public /fc/<slug>-<hex> permalink pages.
curl https://www.stipple.sh/v1/fact-checks/fc_6d22fb4033cbac40Detect AI-written text
POST /v1/detect-ai-text
Estimates the probability that a document's prose was AI-written, with the specific linguistic tells. Abstains (applicable: false) on forms, tables, and scans, where style detection is unreliable. Stateless — nothing is stored.
| Parameter | Type | Description |
|---|---|---|
| file | text | bytes_b64 | url | body | The document or prose to analyse. |
{
"applicable": true,
"probability": 0.92,
"lean": "ai",
"tells": ["uniform paragraph rhythm", "hedged superlatives", ...],
"reasoning": "...",
"limitations": "Probability is a confidence, not a calibrated truth. ..."
}Check text against published sources
POST /v1/check-source-overlap
Finds whether submitted text overlaps text published on the public web — a plagiarism-style check. Exact matches are confirmed against the fetched source page and returned as quotes from both sides with char spans and the URL. A second tier — clearly-labelled model judgements on possible paraphrases, in possible_paraphrases, never mixed with exact matches — is currently disabled pending its evaluation gate: until it passes, results carry an empty list, paraphrase_checked: false, and a note saying so. Reports overlap, never accusations — no match is never proof of originality. Free, like every checker — counted against your weekly limit only when outcome: "assessed"; abstentions (applicable: false) and search outages don't count. Stateless — nothing is stored.
| Parameter | Type | Description |
|---|---|---|
| file | text | bytes_b64 | url | body | The document or prose to check. A url’s own site is excluded from matches. |
{
"outcome": "assessed",
"overlap_band": "notable",
"overlap_ratio": 0.14,
"matches": [{
"tier": "exact",
"source_url": "https://example.com/reference-handbook",
"matched_quote": "...",
"source_quote": "...",
"passage_span": {"start_char": 118, "end_char": 214},
"tokens_matched": 17
}],
"possible_paraphrases": [],
"paraphrase_checked": false,
"note": "Paraphrase comparison is not enabled on this deployment — the exact-match tier still ran.",
"top_sources": [...],
"headline": "Matching published text found at 1 source ...",
"limitations": "Public-web overlap only ... never an originality certificate. ..."
}Extract document fields
POST /v1/extract
Pull structured fields out of any document. A vision model reads up to a few pages and returns the values you ask for. Say what to pull four ways: an ad-hoc fields list (names, or {name, type, description}, where type is text / amount / date / boolean); a template (payslip, tax_invoice, bank_statement, receipt, contract); neither (auto — classify, then apply that type's fields); or auto on an unrecognised type (schema-free — every labelled field). Reads checkboxes (type: boolean). Extraction, not verification — values are what the document shows; the document is never stored.
options turns on extra capabilities, every one off by default — and asking for one this server does not support is an error naming it, never a silent no-op. tables returns whole tables as rows: on a born-digital PDF they are read deterministically from the document's own coordinates (exact cells, merged-cell colspans, no charge for the table pass); on a scan the vision model reads the rows and the table says source: "vlm". grounding adds a bounding box and text_layer_match to every value the PDF's own text layer can locate — a scan has no coordinate source yet, so its values return bbox: null. classify and redact fold the classification verdict and the masked PII inventory into the same call. layout, links and figures return the document's structure; chunks returns retrieval-ready chunks (six strategies, heading path and position on every chunk; tables are kept whole by default); split returns the page ranges of the distinct documents in one file; flag_below lists the fields under a confidence threshold. GET /v1/extract/capabilities (free, no key) is the machine-readable contract for exactly what this server supports.
| Parameter | Type | Description |
|---|---|---|
| file | bytes_b64 | url | body | The document: multipart PDF/image, inline base64, or a public URL (SSRF-guarded). |
| fields | body / query | Optional ad-hoc field list — names, or {name, type, description|question}. As a query param, repeat ?fields=. |
| template | body / query | Optional preset: payslip | tax_invoice | bank_statement | receipt | contract. |
| country | body / query | Optional hint (e.g. AU, IN, US) to sharpen the read. |
| max_pages | body / query | Pages to read per document (default a few; hard ceiling 10). Also caps the charge. |
| options | body / query | Capabilities to turn on: tables, grounding, classify, redact, layout, links, figures, chunks, split, flag_below. JSON body takes an object (tables and chunks tune with nested settings); a query string sets each as a flat param, e.g. ?tables=true&grounding=true. |
| render_scale | body / query | Rasterisation scale for small or dense print: 1.0 | 1.5 | 2.0 (default) | 3.0 | 4.0. |
{
"mode": "fields",
"document_type": "document",
"fields": {
"gross_pay": { "value": 5800, "confidence": 0.95, "page": 1 },
"consent_checkbox": { "value": "checked", "confidence": 0.98, "page": 1 }
},
"not_found": ["middle_name"],
"pages_read": 1,
"page_limit": 3,
"page_count": 1,
"capabilities_used": ["tables.native_pdf"],
"tables": [ { "page": 1, "source": "native_pdf", "header": ["Description", "Amount"],
"json": [ { "Description": "Consulting", "Amount": "1000.00" } ] } ],
"limitations": "Extraction, not verification - values are what the document shows. ..."
}Free, like every checker — counted against your weekly limit once per call regardless of page count or which options (grounding, layout, links, figures, chunks, redact, split, classify) are requested.
Verify identity (AU 100-point)
POST /v1/identity-check
Run an Australian identity check over a set of documents. A vision model reads each one — its type, which identity fields it shows (name / photo / address / signature), and its issue date — then a deterministic engine tallies them against a scheme: afp_100_point (points, the default) or austrac_safe_harbour (category combinations). It reports whether identity is established and exactly what's missing if not. Identity coverage, not a forgery judgment — run /v1/warrants for authenticity. Stateless; documents are never stored.
| Parameter | Type | Description |
|---|---|---|
| files | body, multipart | One or more documents (PDF/image). Or JSON documents[] (below). |
| documents | body, JSON | Array of {"url": "…"} or {"bytes_b64": "…", "filename": "…"} (up to 10). |
| scheme | query | body | afp_100_point (default) or austrac_safe_harbour. |
{
"established": true,
"rule": "points_accumulation",
"points": 110,
"target": 100,
"primaries": 2,
"reason": "Identity established: 110 points with 2 primary document(s).",
"documents": [
{ "classified_type": "australian_passport", "shows": ["N", "P"],
"points": 70, "counts": true, "note": "+70 pts (primary)" },
{ "classified_type": "drivers_licence", "shows": ["N", "A", "P"],
"points": 40, "counts": true, "note": "+40 pts (primary)" }
],
"limitations": "Verification COVERAGE, not a legal identity decision. ..."
}Check a document pack (completeness)
POST /v1/check-pack
Does a set of documents satisfy a checklist? Each document is classified (one cheap page-1 read, hash-cached — never field extraction or multi-page reads), then a deterministic engine maps the types onto the checklist's required slots and reports what's present and what's missing. Supply a named scheme (income_proof, lending_prequal, rental_application) or an ad-hoc requirements checklist. Completeness coverage, not approval — that the right document types are present, not that any is genuine.
| Parameter | Type | Description |
|---|---|---|
| files | body, multipart | One or more documents (PDF/image), up to 12. Or JSON documents[] (below). |
| documents | body, JSON | Array of {"url": "…"} or {"bytes_b64": "…", "filename": "…"}. |
| scheme | query | body | income_proof | lending_prequal | rental_application. |
| requirements | query | body | Ad-hoc checklist: type names, or {key, accepts:[types], optional}. As a query param, repeat ?requirements=. |
{
"scheme": { "id": "lending_prequal", "name": "Lending pre-qualification" },
"complete": false,
"slots": [
{ "key": "proof_of_income", "label": "Proof of income", "satisfied": true, "matched": "payslip.pdf" },
{ "key": "bank_statement", "label": "Recent bank statement", "satisfied": false, "matched": null }
],
"missing": ["bank_statement"],
"documents": [{ "filename": "payslip.pdf", "classified_type": "payslip" }],
"unmatched_documents": [],
"limitations": "Completeness COVERAGE, not approval - that the right document types are present. ..."
}Screen adverse media & sanctions
POST /v1/adverse-media
Screen a person or organisation for adverse media and sanctions exposure (PEP is not screened, and the reply says so). Two layers: a deterministic, corroboration-gated match against four primary sanctions lists (OFAC, EU, UK, AU DFAT), and an entity-disambiguated scan of public news for adverse coverage (fraud, money laundering, bribery, sanctions, enforcement). Same-name articles about other people are surfaced under excluded, and a name-only sanctions hit is flagged possible, never confirmed. Screening coverage, not a determination — a hit means “review”, and “nothing found” is not a clean record.
| Parameter | Type | Description |
|---|---|---|
| name | body | The subject to screen. Provide this, OR a document to read the subject from. |
| dob, country, aliases, employer, role | body | Optional disambiguators — they sharpen matching and cut same-name false positives. |
| file | bytes_b64 | url | body | Alternatively, an identity document (multipart / base64 / public URL) to read the subject from. |
{
"subject": { "name": "Jane Doe", "source": "provided", "country": "AU" },
"sanctions": {
"candidates": [
{ "name": "Jane Doe", "score": 0.93, "strength": "possible",
"corroboration": "name-only", "topics": ["role.pep"], "datasets": 3 }
],
"strong_matches": 0, "possible_matches": 1, "common_name_ambiguity": false
},
"adverse_media": {
"matched_hits": 1, "possible_hits": 0, "excluded_same_name": 2,
"categories": { "financial_crime": 1 },
"hits": [
{ "url": "https://…", "title": "…", "source": "law360.com",
"match": "match", "category": "financial_crime", "severity": "convicted",
"evidence": "found guilty of fraud" }
],
"excluded": [ { "url": "https://…", "title": "…", "reason": "different person (same name)" } ]
},
"risk_flag": "adverse_findings",
"headline": "Jane Doe: 1 possible sanctions match (verify); 1 matched + 0 possible adverse-media hits (fraud); 2 same-name false matches excluded.",
"limitations": "Screening COVERAGE, not a determination. ..."
}Resolve a company, and check its standing
POST /v1/companies/resolve
Work out which Australian company a name or number refers to, and whether it is in good standing. Give a name (Acrux Ltd) or an identifier (ABN, ACN or NZBN). An identifier resolves directly — no name search, no ranking — so an exact number can never land on a near-miss. A name only ever produces candidates.
Read status before candidates. ambiguous means several real companies match, and selected is null on purpose — choosing between them is yours to do, with the evidence in front of you. not_found means every register answered and none has it; inconclusive means one could not be read, so absence proves nothing. coverage lists every source and how well it was read, including the ones that were blocked.
selected.asic carries registration standing from ASIC's own register. An active ABN is not the same as a registered company — they are different registers answering different questions, and a company in external administration can still hold an active ABN. Standing is republished weekly, so every answer carries as_of and age_days. It does not include directors or officeholders.
| Parameter | Type | Description |
|---|---|---|
| query | body | A company name, or an ABN / ACN / NZBN. A number whose check digit fails is reported as a typo, not searched for as a name. |
| jurisdiction | body | Optional hint (AU, NZ). New Zealand identifiers currently return inconclusive — there is no NZ register adapter yet. |
{
"query": "AQC Dartbrook Pty Ltd",
"parsed_as": "name",
"status": "resolved",
"resolved_because": "exact registered-name match, and the only one among 3 results",
"selected": {
"identifiers": { "abn": "46000012813", "acn": "000012813" },
"names": { "legal": "AQC DARTBROOK PTY LTD" },
"abn_status": "Active",
"asic": {
"status": "EXAD",
"status_meaning": "External administration",
"in_good_standing": false,
"registered": "1923-03-01",
"former_names": ["ANGLO COAL (DARTBROOK) PTY LTD", "THE BELLAMBI COAL CO LTD"],
"as_of": "2026-08-24T14:53:11", "age_days": 3,
"caveat": "ASIC standing as of 2026-08-24T14:53:11. This copy of ASIC's file is no longer refreshed, so the status may have changed since; ASIC Connect is the real-time source."
}
},
"candidates": [ /* every match, ranked; match_quality orders them and never selects */ ],
"coverage": [
{ "source": "abr", "status": "ok", "detail": "2 rows of 3 matches" },
{ "source": "asic", "status": "ok", "detail": "4,002,081 companies, 3d old" }
]
}Inspect a document (forensics)
POST /v1/warrants
Runs the full forensic inspection — tampering signals, structure and metadata analysis, arithmetic reconciliation — and returns a warrant: the document's verification record. The warrant is a signal for review, not a fraud verdict.
| Parameter | Type | Description |
|---|---|---|
| file | bytes_b64 | body | The document (PDF or image). URL intake is not available on this endpoint. |
| stream | query, bool | SSE lifecycle stream — recommended for images (deep visual analysis is slower). |
| fresh | query, bool | Bypass the content-hash cache and re-inspect. |
Key response fields: warrant_id, risk_band (low / medium / high / insufficient / error), inspection_quality, recommended_action, summary, signals[].
Cache check
GET /v1/warrants/check?sha256={hex}
Has this exact document already been inspected? Hash the file yourself (SHA-256, lowercase hex) and check before submitting — identical bytes return the existing warrant for free.
Retrieve a warrant
GET /v1/warrants/{warrant_id}
Fetch a stored warrant by id (warrant_ + 16 hex). Add ?format=md for a human-readable Markdown report.
PDF report
GET /v1/warrants/{warrant_id}/report.pdf
An audit-ready, branded PDF scorecard of the warrant — suitable for case files.
Warrant feedback
POST /v1/warrants/{warrant_id}/feedback
Record a reviewer's verdict on a warrant: {"verdict": "up" | "down", "note": "..."}. Feedback improves the engine's precision over time.
Tool feedback
POST /v1/feedback
Thumbs up/down on a fact-check or AI-text result: {"tool": "detect_ai_text" | "verify_references", "verdict": "up" | "down"}, with optional ref and note.
Health
GET /health
Liveness and version: {"status": "ok", "engine_version": "...", ...}
Create an API key
POST /v1/keys
Free and instant, from a signed-in account (the key shares the account's weekly limit): the key is returned once, stored only as a hash, with its key_id. Up to 5 keys per account. See Authentication.
Metered usage
GET /v1/usage
Requires Authorization: Bearer stp_…. Returns your usage this week and when your weekly limit resets — read from the same store that enforces the limit.
Authentication
Authorization: Bearer stp_… on any endpoint. Reads, tender browsing and the sandbox need nothing. An invalid key is a hard 401, never a silent fallback.