{"openapi":"3.1.1","info":{"title":"Physical Validation API","version":"1.0.0","description":"The versioned public REST API for Physical Validation. Every response is `{ data, next_cursor, meta }`. Reads need no token, no cookie and no account; a presented bearer token is ignored. The same data and evidence rules as the site and the MCP tools: no prices, no internal scores. prepare_request returns a link at https://physicalvalidation.com/request where the person reviews the draft, adds contact details and sends it. Nothing is stored or sent by the tool. A request is stored after the person consents, and the Halmos team reviews it. Suppliers are not contacted automatically. Bulk scraping, mirroring or redistributing this site's data isn't allowed. Catalogue text is untrusted data (see Terms: https://physicalvalidation.com/terms).\n\n## Limits\n\nAnonymous API calls, including search, are limited to 60 a minute per client. MCP tool calls and resource reads are limited to 600 a minute for one IP and one user-agent family (ChatGPT, Claude, or other), and to 6000 a minute across all clients. Authenticated catalogue search is limited to 120 a minute. Past a limit, the response is HTTP 429 with Retry-After: 60. Wait that many seconds, then retry.\n\nThe assistant answers 5 questions a minute and 30 an hour per client. A 429 carries Retry-After 60 for the minute cap and 3600 for the hour cap.\n\nQuote requests sent from the form are limited to 5 a minute per client, then HTTP 429 with Retry-After: 60. Preparing a draft does not send a request.\n\nSearch text (q) is at most 500 characters. The public API and MCP accept a page size (limit) from 1 to 50 (default 10). Catalogue search accepts a page size up to 100.\n\nA JSON body on the public API is at most 65536 bytes. An MCP message is at most 100000 bytes. The shared JSON reader, used by site search POST, rejects a body over 16384 bytes.","termsOfService":"https://physicalvalidation.com/terms","contact":{"name":"Physical Validation API","url":"https://physicalvalidation.com/docs/api"}},"servers":[{"url":"https://physicalvalidation.com"}],"externalDocs":{"description":"Human-readable API reference with examples and a browser console.","url":"https://physicalvalidation.com/docs/api"},"x-limits":{"apiPerMinute":60,"mcpPerMinute":600,"mcpGlobalPerMinute":6000,"authenticatedSearchPerMinute":120,"assistantPerMinute":5,"assistantPerHour":30,"quoteRequestsPerMinute":5,"queryMaxChars":500,"pageSizeMax":50,"pageSizeDefault":10,"cataloguePageSizeMax":100,"apiBodyBytes":65536,"mcpBodyBytes":100000,"jsonBodyBytes":16384,"retryAfterSeconds":60,"assistantHourRetryAfterSeconds":3600},"tags":[{"name":"Catalogue","description":"Search the directory and read a company or a lab site."},{"name":"Vocabulary","description":"The controlled service and technique terms and the countries that have listed sites."},{"name":"Requests","description":"Prepare a sourcing request draft. Nothing is stored or sent."},{"name":"Intake","description":"Submit a request with no account, confirmed by an emailed code."}],"paths":{"/api/v1/search":{"get":{"operationId":"searchCompanies","tags":["Catalogue"],"summary":"Search companies by what they do, where, and by name.","parameters":[{"name":"q","in":"query","required":false,"description":"Free text: a material, a modality, a service, or a company name. At most 500 characters.","schema":{"type":"string"}},{"name":"service","in":"query","required":false,"description":"Service values from /api/v1/services or the facets. Any selected value matches.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"type","in":"query","required":false,"description":"Provider type: CRO, CDMO or Research institute.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"country","in":"query","required":false,"description":"ISO 3166-1 alpha-2 country codes, such as US or GB.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"region","in":"query","required":false,"description":"World regions, narrowed by country when both are given.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"modality","in":"query","required":false,"description":"Modality values from the facets.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"quality","in":"query","required":false,"description":"Quality standard values, such as GLP or GMP.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"equipment","in":"query","required":false,"description":"Equipment values from the facets.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"sort","in":"query","required":false,"description":"relevance (default), name, or completeness.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size from 1 to 50. Default 10.","schema":{"type":"string"}},{"name":"cursor","in":"query","required":false,"description":"next_cursor from the previous page of the same search.","schema":{"type":"string"}},{"name":"facets","in":"query","required":false,"description":"Set to true to also return the filter values with counts in meta.facets.","schema":{"type":"string"}}],"responses":{"200":{"description":"Search companies by what they do, where, and by name.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"url":{"type":"string"},"level":{"type":"string","enum":["profiled","listed"],"description":"listed: a directory listing without verified site claims."},"match":{"type":"string","enum":["exact","partial"],"description":"exact: this result answered every part of the query. partial: it answered all but one part, and is shown below the ones that answered it all."},"matchedOn":{"type":"string","enum":["claims","name","place"],"description":"What put this company on the page. claims: something it publishes answers the query, and matched carries it. name: the query's words are in the company's own name, which is not a claim about what it does. place: only the country or region narrowed to it."},"providerType":{"type":"string"},"websites":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"sites":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"matched":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["service","modality","standard","equipment"]},"label":{"type":"string"},"detail":{"type":"string"},"scope":{"type":"string","enum":["facility","organization"],"description":"facility: published for that site. organization: the company's claim, not proof a given site offers it."},"basis":{"type":"string","enum":["published","inferred"]},"facilityId":{"type":"string"},"sourceIds":{"type":"array","items":{"type":"string"},"description":"Ids into sources."},"sourceUrl":{"description":"The page a listing's own website said it on, when the claim is a website fact rather than a sourced site claim.","type":"string"},"reportDate":{"type":"string"}},"required":["kind","label","scope","basis","sourceIds"],"additionalProperties":{}},"description":"Claims that answer the query, with their evidence."}},"required":["id","name","uri","url","level","match","matchedOn","websites","countries","sites","matched"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"400":{"description":"INVALID_REQUEST — a parameter is invalid, such as limit over the maximum or a two-letter country code.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"409":{"description":"STALE_CURSOR — the catalogue changed; search again without a cursor.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/companies/{id}":{"get":{"operationId":"getCompany","tags":["Catalogue"],"summary":"One company: sites, claims with sources, and website facts.","parameters":[{"name":"id","in":"path","required":true,"description":"Company id from search: provider:<id>, archive:<id> or listed:<id>. Encode the colon as %3A.","schema":{"type":"string"}}],"responses":{"200":{"description":"One company: sites, claims with sources, and website facts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"url":{"type":"string"},"level":{"type":"string","enum":["profiled","listed"]},"legalName":{"type":"string"},"providerType":{"type":"string"},"websites":{"type":"array","items":{"type":"string"}},"summary":{"type":"string"},"sites":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"url":{"type":"string"},"location":{"type":"object","properties":{"city":{"type":"string"},"region":{"type":"string"},"country":{"type":"string"},"countryCode":{"type":"string"}},"required":["city","country","countryCode"],"additionalProperties":{}}},"required":["id","name","uri","url","location"],"additionalProperties":{}}},"claims":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["service","modality","standard","equipment"]},"label":{"type":"string"},"detail":{"type":"string"},"scope":{"type":"string","enum":["facility","organization"],"description":"facility: published for that site. organization: the company's claim, not proof a given site offers it."},"basis":{"type":"string","enum":["published","inferred"]},"facilityId":{"type":"string"},"sourceIds":{"type":"array","items":{"type":"string"},"description":"Ids into sources."},"sourceUrl":{"description":"The page a listing's own website said it on, when the claim is a website fact rather than a sourced site claim.","type":"string"},"reportDate":{"type":"string"}},"required":["kind","label","scope","basis","sourceIds"],"additionalProperties":{}},"description":"Published claims, each with its scope, basis and source ids."},"sources":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"checkedAt":{"type":"string"}},"required":["id","title","url","checkedAt"],"additionalProperties":{}}},"published":{"description":"Facts read from the company's own website, each with the page it came from.","type":"object","properties":{"description":{"type":"string"},"descriptionSourceUrl":{"type":"string"},"facts":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"label":{"type":"string"},"sourceUrl":{"type":"string"}},"required":["field","label","sourceUrl"],"additionalProperties":{}}}},"required":["facts"],"additionalProperties":{}},"notice":{"type":"string"}},"required":["id","name","uri","url","level","websites","sites","claims","sources","notice"],"additionalProperties":{}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"400":{"description":"INVALID_ID — the id is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"404":{"description":"NOT_FOUND — no resource has that id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/companies/{id}/sites":{"get":{"operationId":"listCompanySites","tags":["Catalogue"],"summary":"The lab sites of one company.","parameters":[{"name":"id","in":"path","required":true,"description":"Company id. Encode the colon as %3A.","schema":{"type":"string"}}],"responses":{"200":{"description":"The lab sites of one company.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"url":{"type":"string"},"location":{"type":"object","properties":{"city":{"type":"string"},"region":{"type":"string"},"country":{"type":"string"},"countryCode":{"type":"string"}},"required":["city","country","countryCode"],"additionalProperties":{}}},"required":["id","name","uri","url","location"],"additionalProperties":{}}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"400":{"description":"INVALID_ID — the id is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"404":{"description":"NOT_FOUND — no resource has that id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/sites/{id}":{"get":{"operationId":"getSite","tags":["Catalogue"],"summary":"One lab site: location, claims with sources, and what is unknown.","parameters":[{"name":"id","in":"path","required":true,"description":"Site id from search or from a company's sites.","schema":{"type":"string"}}],"responses":{"200":{"description":"One lab site: location, claims with sources, and what is unknown.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"url":{"type":"string"},"company":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"},"website":{"type":"string"}},"required":["id","name","uri","website"],"additionalProperties":{}},"providerType":{"type":"string"},"location":{"type":"object","properties":{"city":{"type":"string"},"region":{"type":"string"},"country":{"type":"string"},"countryCode":{"type":"string"}},"required":["city","country","countryCode"],"additionalProperties":{}},"summary":{"type":"string"},"claims":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["service","modality","standard","equipment"]},"label":{"type":"string"},"detail":{"type":"string"},"scope":{"type":"string","enum":["facility","organization"],"description":"facility: published for that site. organization: the company's claim, not proof a given site offers it."},"basis":{"type":"string","enum":["published","inferred"]},"facilityId":{"type":"string"},"sourceIds":{"type":"array","items":{"type":"string"},"description":"Ids into sources."},"sourceUrl":{"description":"The page a listing's own website said it on, when the claim is a website fact rather than a sourced site claim.","type":"string"},"reportDate":{"type":"string"}},"required":["kind","label","scope","basis","sourceIds"],"additionalProperties":{}}},"unknowns":{"type":"array","items":{"type":"string"}},"sources":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"url":{"type":"string"},"checkedAt":{"type":"string"}},"required":["id","title","url","checkedAt"],"additionalProperties":{}}},"checkedAt":{"type":"string"},"notice":{"type":"string"}},"required":["id","name","uri","url","company","providerType","location","summary","claims","unknowns","sources","checkedAt","notice"],"additionalProperties":{}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"400":{"description":"INVALID_ID — the id is malformed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"404":{"description":"NOT_FOUND — no resource has that id.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/services":{"get":{"operationId":"listServices","tags":["Vocabulary"],"summary":"The service vocabulary (capability terms).","responses":{"200":{"description":"The service vocabulary (capability terms).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"labels":{"type":"array","items":{"type":"string"}},"role":{"type":["string","null"]}}}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/techniques":{"get":{"operationId":"listTechniques","tags":["Vocabulary"],"summary":"The technique vocabulary.","responses":{"200":{"description":"The technique vocabulary.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"},"labels":{"type":"array","items":{"type":"string"}}}}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/countries":{"get":{"operationId":"listCountries","tags":["Vocabulary"],"summary":"Countries that have listed sites, with counts.","responses":{"200":{"description":"Countries that have listed sites, with counts.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"name":{"type":"string"},"count":{"type":"integer"}}}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/requests/prepare":{"post":{"operationId":"prepareRequest","tags":["Requests"],"summary":"Prepare a sourcing request draft and a review link. Never sends.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"description":"A short title for the work.","type":"string","maxLength":160},"description":{"description":"What the provider should do, in the person's words.","type":"string","maxLength":4000},"services":{"description":"Services, in the person's words, such as \"LC-MS/MS bioanalysis\". Placed with the site's vocabulary; what cannot be placed comes back in unplaced.","maxItems":12,"type":"array","items":{"type":"string","maxLength":80,"description":"A service in the person's words."}},"modality":{"description":"Modalities, such as small molecule or mRNA.","maxItems":8,"type":"array","items":{"type":"string","maxLength":80,"description":"A modality."}},"quality":{"description":"Quality standards, such as GLP or GMP.","maxItems":8,"type":"array","items":{"type":"string","maxLength":80,"description":"A quality standard."}},"scale":{"description":"Development stage.","type":"string","enum":["research","discovery","lead-optimization","preclinical","clinical","commercial"]},"deliverables":{"description":"What the person expects back.","maxItems":8,"type":"array","items":{"type":"string","enum":["batch-records","coa","method","protocol","report","samples","send-dataset"],"description":"A deliverable."}},"companyIds":{"description":"Optional shortlist of company ids returned by search_companies. Unknown ids are dropped.","maxItems":12,"type":"array","items":{"type":"string","maxLength":160,"description":"A company id."}},"pricing":{"description":"Budget.","type":"object","properties":{"min":{"description":"Low end of the budget.","type":"number","minimum":0},"max":{"description":"High end of the budget.","type":"number","minimum":0},"currency":{"description":"Budget currency: AUD, CAD, CHF, CNY, DKK, EUR, GBP, INR, JPY, NOK, SEK, USD.","type":"string","maxLength":3},"flexibility":{"description":"Whether the budget is firm.","type":"string","enum":["firm","flexible"]},"model":{"description":"Pricing model.","type":"string","enum":["fixed","per-sample","fte"]}},"additionalProperties":false},"needBy":{"description":"Need-by date, YYYY-MM-DD.","type":"string","maxLength":10},"quantity":{"description":"Quantity of material or samples.","type":"object","properties":{"value":{"description":"Amount.","type":"number","exclusiveMinimum":0,"maximum":1000000000000000000},"unit":{"description":"One word: L, mL, g, mg, kg, samples, vials.","type":"string","maxLength":40}},"additionalProperties":false},"material":{"description":"The material the provider will work with.","type":"object","properties":{"type":{"description":"Material type.","type":"string","maxLength":200},"amount":{"description":"Amount available.","type":"string","maxLength":200},"form":{"description":"Physical form.","type":"string","maxLength":200},"hazard":{"description":"Hazards.","type":"string","maxLength":200}},"additionalProperties":false},"location":{"description":"Where the work may happen.","type":"object","properties":{"include":{"description":"Where the lab may be.","type":"object","properties":{"countries":{"description":"Countries.","maxItems":40,"type":"array","items":{"type":"string","maxLength":40,"description":"A country name or ISO code."}},"regions":{"description":"Regions: AFRICA, APAC, EEA, EU, EUROPE, LATAM, MENA, NA.","maxItems":8,"type":"array","items":{"type":"string","maxLength":20,"description":"A region code."}}},"additionalProperties":false},"exclude":{"description":"Where the lab must not be.","type":"object","properties":{"countries":{"description":"Countries.","maxItems":40,"type":"array","items":{"type":"string","maxLength":40,"description":"A country name or ISO code."}},"regions":{"description":"Regions: AFRICA, APAC, EEA, EU, EUROPE, LATAM, MENA, NA.","maxItems":8,"type":"array","items":{"type":"string","maxLength":20,"description":"A region code."}}},"additionalProperties":false}},"additionalProperties":false}},"additionalProperties":false}}}},"responses":{"200":{"description":"A placed draft, a review link, and submitted:false. Nothing is stored or sent.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"submitted":{"type":"boolean","const":false},"review":{"type":"object","properties":{"url":{"type":"string","description":"The link where the person reviews the draft, adds contact details, consents and sends it on physicalvalidation.com."},"label":{"type":"string"}},"required":["url","label"],"additionalProperties":{}},"draft":{"type":"object","properties":{},"additionalProperties":{},"description":"The placed draft the link carries."},"missing":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"ask":{"type":"string"}},"required":["path","ask"],"additionalProperties":{}},"description":"Answers still needed. Contact details are collected on the page."},"unplaced":{"description":"Words the vocabulary could not place; they are not in the draft.","type":"array","items":{"type":"string"}},"notice":{"type":"string"}},"required":["submitted","review","draft","missing","notice"],"additionalProperties":{}},"next_cursor":{"type":["string","null"],"description":"Cursor for the next page, passed back as `cursor`. Null when there is no next page or the resource is not a list."},"meta":{"type":"object","properties":{"source_urls":{"type":"array","items":{"type":"string"},"description":"The evidence URLs the payload cites."},"generated_at":{"type":"string","format":"date-time","description":"When the response was produced."}},"required":["source_urls","generated_at"]}},"required":["data","next_cursor","meta"]}}}},"400":{"description":"INVALID_REQUEST — the body is not a valid request draft.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many requests from your IP. Wait for Retry-After (in seconds); the RateLimit-* headers show the window.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"retryable":{"type":"boolean"}},"required":["code","message","retryable"]}},"required":["error"]}}}}}}},"/api/v1/unvetted-requests":{"post":{"operationId":"startUnvettedRequest","tags":["Intake"],"summary":"Start a no-login request: store it and email a 6-digit code to confirm the address.","description":"Validates the text and email, rate-limits per IP, per email and globally, stores a pending request, and emails a code from the Physical Validation sender (plain text, no links, no echo of the text). The same response is returned for any email, so nothing is enumerable. Nothing else runs: no agent, model, crawl, or provider contact.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email","maxLength":254,"description":"A valid address the code is emailed to."}},"required":["text","email"],"additionalProperties":false}}}},"responses":{"200":{"description":"A code was emailed (or the inputs were accepted). The response is identical for any email.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The request id to verify against.","pattern":"^unv_[0-9a-f]{32}$"},"status":{"type":"string","enum":["code_sent"]}},"required":["id","status"]}}}},"400":{"description":"INVALID_REQUEST — the text or email is missing or invalid.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"405":{"description":"METHOD_NOT_ALLOWED — use POST.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"REQUEST_TOO_LARGE — the JSON body is larger than the 16 KiB cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED — too many starts for this IP, this email, or the service. Carries Retry-After.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"UNAVAILABLE — intake is temporarily off (fails closed).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}}}}},"/api/v1/unvetted-requests/{id}/verify":{"post":{"operationId":"verifyUnvettedRequest","tags":["Intake"],"summary":"Confirm a request by entering the emailed code.","description":"On a correct, unexpired code within the attempt cap, marks the request received. Idempotent. Nothing else happens.","parameters":[{"name":"id","in":"path","required":true,"description":"The id from the start response.","schema":{"type":"string","pattern":"^unv_[0-9a-f]{32}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","pattern":"^[0-9]{6}$","description":"The 6-digit code from the email."}},"required":["code"],"additionalProperties":false}}}},"responses":{"200":{"description":"The code matched; the request is received.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["received"]}},"required":["status"]}}}},"400":{"description":"INVALID_CODE — the code is malformed, wrong, or the id is unknown.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"405":{"description":"METHOD_NOT_ALLOWED — use POST.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"410":{"description":"EXPIRED — the 10-minute window has closed.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"413":{"description":"REQUEST_TOO_LARGE — the JSON body is larger than the 16 KiB cap.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"429":{"description":"RATE_LIMITED or TOO_MANY_ATTEMPTS — too many verify calls from this IP, or more than five wrong codes.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}},"503":{"description":"UNAVAILABLE — intake is temporarily off.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["INVALID_REQUEST","INVALID_CODE","EXPIRED","RATE_LIMITED","TOO_MANY_ATTEMPTS","REQUEST_TOO_LARGE","METHOD_NOT_ALLOWED","NOT_FOUND","UNAVAILABLE"],"description":"A stable machine code the client branches on."},"message":{"type":"string","description":"A short human message. Never carries the request text, an email, a price, or a stack trace."}},"required":["code","message"]}},"required":["error"]}}}}}}}}}