{"openapi":"3.1.0","info":{"title":"Proofwire","version":"1.0.0","summary":"Phone, email and IP validation with calibrated confidence and a full evidence trail.","description":"Every verdict carries the confidence behind it and the list of signals that produced it.\n\nThree billing rules apply to every endpoint:\n  1. Credits never expire.\n  2. An inconclusive verdict costs nothing.\n  3. A cached repeat within the freshness window costs nothing.\n\nVerdicts are three-state. `unknown` is a real answer, not an error: it means the evidence\nlanded inside the uncertainty band, most often because the mail server accepts every\naddress. Handle it explicitly rather than treating it as a failure.","contact":{"name":"Proofwire support","url":"https://proofwire.app/support"}},"servers":[{"url":"http://localhost:4000","description":"Local development"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Send `Authorization: Bearer pk_live_...`. Test keys (`pk_test_...`) hit the sandbox fixtures and are never billed."}},"schemas":{"Evidence":{"type":"object","description":"One observation from one provider. The full list is returned with every verdict.","properties":{"source":{"type":"string","example":"mx"},"signal":{"type":"string","example":"mx_records_present"},"detail":{"type":"string","description":"Human-readable explanation, safe to show to an end user."},"weight":{"type":"number","description":"Contribution to the verdict in log-odds. Positive pushes toward valid, negative toward invalid, 0 is informational and affects only the risk score."},"observedAt":{"type":"string","format":"date-time","description":"When the underlying data was observed, not when you called us."},"freshnessDays":{"type":"integer","nullable":true,"description":"Age of the source data. Stale evidence is automatically discounted."}}},"LookupResult":{"type":"object","properties":{"verdict":{"type":"string","enum":["valid","invalid","unknown"]},"confidence":{"type":"number","minimum":0,"maximum":1,"description":"Calibrated P(valid). Below 0.62 and above 0.35 the verdict is `unknown`."},"risk":{"type":"integer","minimum":0,"maximum":100,"description":"Abuse risk, scored independently of validity. A real number can be risky and a disposable inbox can be deliverable."},"attributes":{"type":"object","description":"Subject-specific fields: carrier, line type, MX hosts, ASN and so on."},"evidence":{"type":"array","items":{"$ref":"#/components/schemas/Evidence"}},"trace":{"type":"array","description":"Which providers ran, which were skipped and why. Shows exactly what you paid for."},"billing":{"type":"object","properties":{"credits_charged":{"type":"integer"},"reason":{"type":"string","description":"Plain-language explanation of the charge, on every response."},"balance_after":{"type":"integer"}}}}},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string","description":"Written to be actionable, including what to do next."}}}}}}},"paths":{"/v1/phone":{"post":{"summary":"Validate a phone number","operationId":"validate_phone","tags":["Validation"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Unique value per distinct request. A retry with the same key replays the original response instead of spending again -- send one on every call if your client retries on timeout."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"type":"string"},"confidence_target":{"type":"number","minimum":0.7,"maximum":0.99,"default":0.9,"description":"How certain to get before stopping. A higher target climbs more tiers, costs more latency and may add the deep-probe surcharge."}}},"example":{"value":"+447700900000"}}}},"responses":{"200":{"description":"A verdict, with its evidence and its price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupResult"}}}},"400":{"description":"Malformed request, or an input a test key does not recognise."},"402":{"description":"Balance exhausted or your own spend cap reached. Nothing was charged."},"409":{"description":"Idempotency-Key reused with a different body."},"429":{"description":"Burst rate limit. Retry-After says when. Monthly volume is unaffected."}}}},"/v1/email":{"post":{"summary":"Validate an email address","operationId":"validate_email","tags":["Validation"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Unique value per distinct request. A retry with the same key replays the original response instead of spending again -- send one on every call if your client retries on timeout."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"type":"string"},"confidence_target":{"type":"number","minimum":0.7,"maximum":0.99,"default":0.9,"description":"How certain to get before stopping. A higher target climbs more tiers, costs more latency and may add the deep-probe surcharge."}}},"example":{"value":"someone@example.com"}}}},"responses":{"200":{"description":"A verdict, with its evidence and its price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupResult"}}}},"400":{"description":"Malformed request, or an input a test key does not recognise."},"402":{"description":"Balance exhausted or your own spend cap reached. Nothing was charged."},"409":{"description":"Idempotency-Key reused with a different body."},"429":{"description":"Burst rate limit. Retry-After says when. Monthly volume is unaffected."}}}},"/v1/ip":{"post":{"summary":"Validate and profile an IP address","operationId":"validate_ip","tags":["Validation"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Unique value per distinct request. A retry with the same key replays the original response instead of spending again -- send one on every call if your client retries on timeout."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["value"],"properties":{"value":{"type":"string"},"confidence_target":{"type":"number","minimum":0.7,"maximum":0.99,"default":0.9,"description":"How certain to get before stopping. A higher target climbs more tiers, costs more latency and may add the deep-probe surcharge."}}},"example":{"value":"8.8.8.8"}}}},"responses":{"200":{"description":"A verdict, with its evidence and its price.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupResult"}}}},"400":{"description":"Malformed request, or an input a test key does not recognise."},"402":{"description":"Balance exhausted or your own spend cap reached. Nothing was charged."},"409":{"description":"Idempotency-Key reused with a different body."},"429":{"description":"Burst rate limit. Retry-After says when. Monthly volume is unaffected."}}}},"/v1/batches":{"post":{"summary":"Create a batch and get a quote","description":"Returns a cost ceiling and stops. Nothing is charged until you confirm, and the final charge lands at or below the quote because duplicates, cache hits and inconclusive rows cost nothing.","tags":["Batches"],"responses":{"201":{"description":"Batch created and quoted, awaiting confirmation."}}},"get":{"summary":"List batches","tags":["Batches"],"responses":{"200":{"description":"Recent batches."}}}},"/v1/batches/{id}/confirm":{"post":{"summary":"Accept the quote and start processing","description":"The spend cap is re-checked while the job runs, so a batch that would cross your ceiling stops there rather than finishing and presenting the overage afterwards.","tags":["Batches"],"responses":{"200":{"description":"Batch started."}}}},"/v1/batches/{id}":{"get":{"summary":"Batch status and results","description":"Reports the quoted cost and the actual cost side by side.","tags":["Batches"],"responses":{"200":{"description":"Progress and results."}}},"delete":{"summary":"Cancel a batch","description":"Only the rows already processed are charged.","tags":["Batches"],"responses":{"200":{"description":"Cancelled."}}}},"/v1/usage":{"get":{"summary":"Usage and balance","description":"Includes `inconclusive_lookups_not_billed` -- how many answers we declined to charge you for.","tags":["Account"],"responses":{"200":{"description":"Usage summary."}}}},"/v1/billing/ledger":{"get":{"summary":"Full credit history","description":"Every movement with its reason. Derived from an append-only ledger, not a mutable counter.","tags":["Billing"],"responses":{"200":{"description":"Ledger entries."}}}},"/v1/billing/spend-cap":{"put":{"summary":"Set a hard monthly ceiling","description":"Requests are refused past it rather than billed. Effective immediately.","tags":["Billing"],"responses":{"200":{"description":"Cap updated."}}}},"/v1/billing/cancel":{"post":{"summary":"Cancel the subscription","description":"One call, effective immediately. No notice period, no written request, no retention flow. Remaining credits stay usable and API keys keep working. Returns a confirmation reference to keep.","tags":["Billing"],"responses":{"200":{"description":"Cancelled, with a confirmation reference."}}}},"/v1/billing/credits":{"post":{"summary":"Add credits","description":"Purchased credits never expire. The response says so explicitly.","tags":["Billing"],"responses":{"200":{"description":"New balance."}}}},"/v1/keys":{"get":{"summary":"List API keys","tags":["Keys"],"responses":{"200":{"description":"Keys by prefix. Full values are never retrievable."}}},"post":{"summary":"Create an API key","description":"The full key is returned once and never again.","tags":["Keys"],"responses":{"201":{"description":"Key created."}}}},"/v1/keys/{id}":{"delete":{"summary":"Revoke a key","description":"Effective immediately.","tags":["Keys"],"responses":{"200":{"description":"Revoked."}}}},"/v1/account/audit":{"get":{"summary":"Account audit trail","description":"Plan changes, cancellations, cap adjustments and key events, timestamped.","tags":["Account"],"responses":{"200":{"description":"Audit entries."}}}},"/benchmark":{"get":{"summary":"Public accuracy benchmark","description":"Live results against the published labelled dataset, with structural and network accuracy reported separately and an explicit statement of which upstreams were live. Reproduce it with npm run bench. No authentication.","tags":["Public"],"security":[],"responses":{"200":{"description":"Benchmark report with per-case results."}}}},"/pricing":{"get":{"summary":"Full price list","description":"Published in full, including volume rates. No sales call required to learn what a lookup costs.","tags":["Public"],"security":[],"responses":{"200":{"description":"Prices and guarantees."}}}},"/status":{"get":{"summary":"Live status","description":"Computed from real traffic on every request, not a manually flipped switch.","tags":["Public"],"security":[],"responses":{"200":{"description":"Current status."}}}},"/sandbox":{"get":{"summary":"Sandbox fixtures","description":"Deterministic inputs accepted by test keys -- 13 fixtures covering every branch, including catch-all and greylisting.","tags":["Public"],"security":[],"responses":{"200":{"description":"Fixture catalogue."}}}}}}