audit.domains Bulk API
Multi-domain appraisal with reserve-then-settle metering
Multi-domain appraisal with reserve-then-settle metering
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/audit-domains:audit-domains-bulk-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: audit.domains public Bulk API
version: 1.3.0
description: 'Deterministic domain appraisals, comparable sales, bulk valuation, and recorded-sales search. Document version 1.3.0. Since 1.3.0, `GET /api/v1/sales` serves only the curated verified corpus; the legacy `source=ledger` research source was retired so no browser-reachable database RPC can bypass the paid row-level research boundary. Since 1.0.0 the surface gained `GET /api/v1/usage`, a per-key request limiter (answered with `Retry-After` and `X-RateLimit-Reset`), and a monthly API call allowance separate from the site''s appraisal credits - reported by the `X-Credits-*` headers and refused with `402 api_quota_exhausted`. The path prefix stays `/v1`: the locked appraisal projection has not changed, key for key. What this document covers: the five key-authenticated paths listed below - `/api/v1/appraise`, `/api/v1/comps`, `/api/v1/bulk`, `/api/v1/usage`, `/api/v1/sales`. One further route exists under the same prefix and is NOT part of the public contract: `/api/v1/historical-sales/snapshots/active` is an internal serving seam for the website''s own comparables snapshot - anonymous, unmetered, edge-cached, does not use the error envelope described here, and may change without a version bump. Do not build against it. No key? A keyless $5 card lane for single appraisals is described in https://audit.domains/api/openapi-machine.json (POST https://audit.domains/api/machine/resolve); it is not part of this contract. API access is included with paid plans: every request requires an `ad_live_` API key (create one at https://audit.domains/profile?tab=api), keys work only on paid plans, and appraisal-producing calls consume the account''s monthly API allowance. That allowance is SEPARATE from the appraisal credits the website meters: spending one does not spend the other, and a large batch through the API leaves the site quota untouched. When it is spent, calls answer `402 api_quota_exhausted` until the monthly reset. `X-Credits-Limit` / `X-Credits-Remaining` / `X-Credits-Reset` response headers report that allowance and the date it refills, on every response that reached the meter (a 200, or a 402 for a spent allowance); requests rejected before metering - a malformed body, a missing key, a per-IP 429 - carry no meter to report, and `GET /api/v1/usage` reads the same figures without spending a call. `X-RateLimit-Limit` / `X-RateLimit-Remaining` currently repeat those credit figures; those names will be redefined to report the per-key request limiter after an announced notice period of at least 30 days. Key scopes: a key is created with `read` or `write` scope. Endpoints that spend an API call (`/appraise`, `/comps`, `/bulk`) require a `write` key and answer `403 insufficient_scope` otherwise; `/sales` and `/usage` accept either. Issue read-scoped keys to anything that only searches recorded sales or polls the allowance, so a leaked key from that integration cannot spend the account''s API allowance. Request limits: two limiters apply, and the tighter one decides. A per-address token bucket runs BEFORE the key is verified and is sized per endpoint - `/appraise` and `/comps` allow a burst of 10 then about 30 requests per minute, `/bulk` a burst of 4 then about 6 per minute, `/sales` and `/usage` a burst of 30 then about 60 per minute. Behind it, each key has a fixed limit per 60-second window, by plan: Plus 60/min, Pro 120/min, Max 300/min. Because the per-address bucket sits in front, one address cannot reach the per-key figure on `/appraise`, `/comps` or `/bulk` at all: on `/appraise` a single address is held near 30/min whatever the plan allows, and the Max figure of 300/min is reachable only by calling from several addresses. Both limiters answer `429 rate_limited` with `Retry-After` (seconds); only the per-key limiter also sends `X-RateLimit-Reset` (Unix seconds), so the presence of that header is what distinguishes the two. Determinism guarantee: the same domain on the same engine version always returns the same result.'
contact:
url: https://audit.domains/contact
termsOfService: https://audit.domains/terms
servers:
- url: https://audit.domains
security:
- bearerKey: []
- headerKey: []
tags:
- name: Bulk
description: Multi-domain appraisal with reserve-then-settle metering
paths:
/api/v1/bulk:
post:
summary: Appraise up to 20 domains (1 API call per domain, write scope; plan must…
operationId: bulkAppraise
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- domains
properties:
domains:
type: array
items:
type: string
pattern: ^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$
maxLength: 253
minItems: 1
maxItems: 20
responses:
'200':
description: Per-domain results. Calls are reserved from the monthly API allowance for the batch up front; if the allowance does not cover every domain, the ones beyond it report quota_exhausted and were never charged, and calls for domains the engine failed on are refunded. A per-domain failure is reported here, not as a 4xx/5xx for the whole call.
headers:
X-Request-Id:
$ref: '#/components/headers/X-Request-Id'
X-Credits-Limit:
$ref: '#/components/headers/X-Credits-Limit'
X-Credits-Remaining:
$ref: '#/components/headers/X-Credits-Remaining'
X-Credits-Reset:
$ref: '#/components/headers/X-Credits-Reset'
X-RateLimit-Limit:
$ref: '#/components/headers/X-RateLimit-Limit'
X-RateLimit-Remaining:
$ref: '#/components/headers/X-RateLimit-Remaining'
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
type: object
properties:
domain:
type: string
ok:
type: boolean
appraisal:
$ref: '#/components/schemas/Appraisal'
error:
type: string
enum:
- appraisal_failed
- quota_exhausted
description: Present when ok is false. appraisal_failed = the engine threw for this domain and its call was refunded. quota_exhausted = the allowance did not cover this domain, so it was never charged and never processed.
requestId:
type: string
description: Correlation id for this call; the same value as the X-Request-Id header.
'400':
$ref: '#/components/responses/BadRequestBulk'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
$ref: '#/components/responses/PaymentRequired'
'403':
$ref: '#/components/responses/InsufficientScope'
'405':
$ref: '#/components/responses/MethodNotAllowed'
'429':
$ref: '#/components/responses/RateLimited'
'503':
$ref: '#/components/responses/AuthUnavailable'
tags:
- Bulk
components:
schemas:
Comp:
type: object
properties:
domain:
type: string
priceUsd:
type: number
year:
type: string
venue:
type:
- string
- 'null'
similarity:
type: integer
minimum: 0
maximum: 100
basis:
type:
- string
- 'null'
description: Why this sale was matched, naming the channel the matcher scored on (shared keyword, word family, same industry, structural cohort, spelling, length/zone). null means the basis was not recorded for this comp - the match is unexplained, not explained by something unstated.
Error:
type: object
properties:
error:
type: string
description: Stable machine-readable code; the only field a client should match on. The full catalogue is published at https://audit.domains/developers#errors
message:
type: string
description: Human-readable and safe to display; never contains internals.
detail:
type: string
description: The actionable next step, when there is one.
requestId:
type: string
description: Correlation id for this request, always present; also sent as the X-Request-Id header on every response. Quote it in support requests.
docsUrl:
type: string
format: uri
description: Deep link to this code's entry in the published error catalogue.
retryable:
type: boolean
description: Whether repeating the identical request can succeed.
plan:
type: string
description: The account's plan id. Present on the per-key 429 rate_limited, on 403 insufficient_scope, and on the 402 codes.
scope:
type: string
description: The scope the key was created with. Present on 403 insufficient_scope.
requiredScope:
type: string
description: The scope this endpoint needs. Present on 403 insufficient_scope.
max:
type: integer
description: Maximum domains one bulk call accepts. Present on 400 too_many_domains.
allowed:
type: array
items:
type: string
description: Methods this path serves, matching the Allow header. Present on 405 method_not_allowed.
source:
type: string
description: The sales source that is not published. Present on 503 source_unavailable.
withoutKey:
type: array
items:
$ref: '#/components/schemas/FreeRoute'
description: What works with no key. Present on 401 key_required.
plans:
type: array
items:
$ref: '#/components/schemas/PlanOffer'
description: The plans whose keys can call this API, cheapest first. Present on 401 key_required and 402 paid_plan_required.
planTerms:
type: string
description: Whether a paid term renews. Present on 401 key_required.
pricing:
type: string
format: uri
description: The keyless API index, which carries the same plans. Present on 401 key_required.
required:
- error
- message
- requestId
- docsUrl
- retryable
Appraisal:
type: object
description: The locked v1 projection. Keys are contract-locked; new keys only arrive with a version bump.
properties:
domain:
type: string
valueUSD:
type: number
valueRange:
type: object
properties:
low:
type: number
mid:
type: number
high:
type: number
tier:
type: string
confidence:
type: number
factors:
type: array
items:
$ref: '#/components/schemas/Factor'
methodologyVersion:
type:
- string
- 'null'
engineVersion:
type: string
comps:
type: array
items:
$ref: '#/components/schemas/Comp'
maxItems: 5
appraisalHash:
type: string
description: SHA-256 over {commitment, result, hashVersion} - the appraisal's stable id (ODVS).
inputCommitment:
type: string
description: SHA-256 over {domain, engineId, engineVersion} - identity of the computation (ODVS).
resultDigest:
type: string
description: SHA-256 over the canonical numeric outputs (ODVS).
generatedAt:
type: string
format: date-time
dataEdition:
type:
- string
- 'null'
description: Version of the comparables snapshot in effect, when known.
Factor:
type: object
properties:
key:
type: string
label:
type: string
score:
type: integer
minimum: 0
maximum: 100
PlanOffer:
type: object
description: One paid plan whose key can call /api/v1, derived from the billing configuration (the same figures as https://audit.domains/pricing).
properties:
id:
type: string
description: Plan id, as /checkout?plan= names it.
name:
type: string
usdPerTerm:
type: number
description: Price of one term in USD.
term:
type: string
enum:
- month
monthlyCalls:
type: integer
description: /api/v1 calls included per term; a separate allowance from site appraisals.
usdPerCall:
type: number
description: Effective USD per included call, rounded to 4 places.
burstPerMinute:
type: integer
description: Per-key request limit per minute.
checkout:
type: string
format: uri
description: Where a person signs in and pays. An agent cannot complete it.
required:
- id
- name
- usdPerTerm
- term
- monthlyCalls
- usdPerCall
- burstPerMinute
- checkout
FreeRoute:
type: object
description: A route that works with no key. {name} stands for the domain.
properties:
what:
type: string
how:
type: string
required:
- what
- how
headers:
X-Request-Id:
description: Correlation id for this request, on every response including successes. Quote it in support requests.
schema:
type: string
X-Credits-Remaining:
description: API calls left in the current monthly allowance, after this call.
schema:
type: integer
X-Credits-Reset:
description: 'ISO 8601 instant the allowance refills: the first instant of the next UTC month.'
schema:
type: string
format: date-time
X-RateLimit-Remaining:
description: Currently repeats X-Credits-Remaining. Read X-Credits-Remaining for the allowance.
schema:
type: integer
X-RateLimit-Limit:
description: Currently repeats X-Credits-Limit. Read X-Credits-Limit for the allowance.
schema:
type: integer
X-Credits-Limit:
description: Monthly API call allowance for this account's plan.
schema:
type: integer
responses:
MethodNotAllowed:
description: method_not_allowed - this path does not serve that HTTP method. The `Allow` response header and the body's `allowed` array both list the methods it does serve.
headers:
Allow:
description: Comma-separated methods this path serves.
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
PaymentRequired:
description: 'paid_plan_required (the account is on the free plan, which does not include API access), upgrade_required (the plan does not include the requested feature - only the bulk endpoint is gated this way), or a spent allowance.
The code for a spent monthly API allowance is `api_quota_exhausted`. A deployment not yet running the separate API credit pool answers the older `quota_exhausted` for the same condition, so a client should treat both as "allowance spent" and branch the same way. Either way the website''s appraisal credits are a separate allowance and are unaffected by API calls.
The X-Credits-* headers are present on the two allowance codes, which are refused after the meter was read, and absent on paid_plan_required and upgrade_required, which are refused before it.'
headers:
X-Credits-Limit:
$ref: '#/components/headers/X-Credits-Limit'
X-Credits-Remaining:
$ref: '#/components/headers/X-Credits-Remaining'
X-Credits-Reset:
$ref: '#/components/headers/X-Credits-Reset'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
InsufficientScope:
description: insufficient_scope - the endpoint spends an API call and the key has read scope only. Create a write-scoped key at https://audit.domains/profile?tab=api. The body carries `scope` (the key's) and `requiredScope`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: key_required (no key sent) or invalid_key (unknown, revoked, or expired). key_required also carries withoutKey (what works with no key) and plans (what a key costs).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
headers:
WWW-Authenticate:
description: Bearer realm="audit.domains" on key_required; adds error="invalid_token" on invalid_key (RFC 6750).
schema:
type: string
RateLimited:
description: rate_limited - either the per-address token bucket in front of key verification tripped, or the key is over its plan's per-minute limit (Plus 60, Pro 120, Max 300). See the request-limits note in the document description for the per-endpoint per-address figures. Both carry Retry-After; only the per-key limiter carries X-RateLimit-Reset and a `plan` field in the body, so that header is what tells you which limiter refused the call.
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
X-RateLimit-Reset:
description: Unix seconds at which the current 60-second window ends. Present when the per-key limiter tripped.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
BadRequestBulk:
description: invalid_json (the body is not valid JSON), domains_required (the `domains` array is missing or empty), too_many_domains (more entries than one call takes - the body's `max` field gives the maximum), or invalid_domain (an entry is not a registrable hostname).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
AuthUnavailable:
description: api_auth_unavailable - key verification is unreachable; the API fails closed rather than serving unmetered.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
securitySchemes:
bearerKey:
type: http
scheme: bearer
description: 'Authorization: Bearer ad_live_...'
headerKey:
type: apiKey
in: header
name: x-api-key