Full HTTP reference for the extractbrand API: fetch a brand's logos, colour roles, typography and voice for any domain as JSON, with per-endpoint parameters, status codes and response shapes.
OpenAPI 3.1 · version v1
Start at https://extractbrand.dev/quickstart — sign up, mint a key, make one request.
Design-system-grade brand data for any domain: colours, typography, logos, imagery and voice, extracted from the live site and served from a shared cache.
Every shape on this page is generated from the Zod schemas the runtime validates against, so the reference cannot describe a response the API does not send.
Keys begin with the eb_ prefix. Create one from your dashboard — sign up first if you have no account. Treat a key like a password: it carries the full access of the organization it belongs to. The key also determines which organization you act on — there is no organization_id parameter anywhere in this API.
Cache-first, with a job for the misses
GET /v1/brands/{domain} answers from a shared cache. A domain someone has already extracted answers 200 in milliseconds. A domain nobody has extracted yet answers 202 with a job, and you re-request the same URL to collect the result — the job envelope's poll_url points back at it. Pass ?wait=N to hold the request open for up to 25 seconds so a first-time domain can answer 200 in one call.
An entry lives for 30 days. Past that it is served with cache_status: "stale" while a refresh runs behind it, so you are never blocked on an expiry. cache_status tells you what latency to expect, never what you were charged — the charge is the same either way.
Quota
Every answered call costs one unit — a domain we already hold and a domain nobody has read yet cost the same, because the meter counts what we serve you rather than what we had to run. POST /v1/brands/{domain}/refresh costs more, since it always forces a fresh extraction. Every 200 and 202 carries the organization's standing in its quota block. A spent quota answers 429 with code quota_exceeded.
Errors
Every non-2xx body on every endpoint is the same shape, with a code from one closed set:
{
"error": {
"code": "invalid_domain",
"message": "The provided domain is invalid or could not be resolved."
}
}
Contract extras live inside that same error object, never beside it — a 429 carries error.retry_after_seconds, a 422 carries error.failure_reason. A 429 also sets the standard Retry-After header, from the same number.
Brands
Read and refresh brand data for a domain. Brand data is derived from the public web and shared across every organization, so a domain another customer has already extracted answers instantly and for free.
GET/v1/brands/{domain}
Get a brand
Returns the cached brand for a domain. A fresh or stale cache entry answers 200 immediately. A domain nobody has extracted yet answers 202 with a job, and the caller re-requests this same URL to collect the result — poll_url in the job envelope points back here. Add ?wait=N to hold the request open for up to N seconds so a first-time domain can answer 200 in a single call. A domain whose owner has proved control of it is served from its record and is never revalidated, so it reports cache_statusstale indefinitely once its entry passes its lifetime; the record is maintained by its owner rather than by measurement, and provenance reports which fields they authored.
Parameters
waitquery · optional — Seconds to hold the request open waiting for an in-flight extraction to finish, so a first-time domain can answer 200 in one call. Clamped to 25; a value that is not a whole number of at least 1 is rejected with 400. Omit it for an immediate 202.
domainpath · required — The domain to look up, without a scheme. A host that redirects elsewhere is resolved to its landing host, and that host is the cache key the answer is keyed on.
Responses
200BrandResponse — The cached brand. cache_status is hit within the entry's 30-day lifetime and stale past it — a stale entry is served immediately while a refresh runs behind it.
202JobResponse — Extraction is under way. Re-request this URL until it answers 200.
400ApiError — invalid_domain when the path segment is not a resolvable domain. invalid_parameter when ?wait is not a whole number of seconds of at least 1.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
404ApiError — not_found — this domain is maintained by its owner and we hold no record to serve. Nothing is extracted for it so the answer does not change on retry.
422ExtractionFailedError — extraction_failed — this domain failed recently and is inside its negative-result window so it is not re-attempted yet.
429RateLimitedError — rate_limited when the API key exceeds its request window. quota_exceeded when the organization's monthly extraction quota is spent.
451ApiError — removed — this brand was removed at the request of its owner and is no longer served. The refusal is final for as long as the removal stands; retrying does not help.
500ApiError — internal_error — the request authenticated but the service failed to answer it.
POST/v1/brands/{domain}/refresh
Refresh a brand
Forces a new extraction for a domain even when a fresh cache entry exists, and always answers 202 — collect the result from GET /v1/brands/{domain}, which poll_url points at. A refresh already under way for this domain is joined rather than duplicated, and costs nothing. Refreshes are capped at one per domain per hour and ten per organization per day, on top of the monthly extraction quota.
Parameters
domainpath · required — The domain to look up, without a scheme. A host that redirects elsewhere is resolved to its landing host, and that host is the cache key the answer is keyed on.
Responses
202JobResponse — The refresh was accepted, or an in-flight extraction for this domain was joined.
400ApiError — invalid_domain — the path segment is not a resolvable domain.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
402ApiError — refresh_not_included — this plan's allowance is smaller than what an explicit refresh always costs. Upgrading resolves it.
409ApiError — brand_claimed — this domain is maintained by its owner and is no longer re-extracted on request. The refusal costs neither a refresh window nor a quota unit.
429RateLimitedError — refresh_rate_limited when a refresh window for this domain or organization is spent. quota_exceeded when the monthly extraction quota is spent. rate_limited when the API key exceeds its request window.
451ApiError — removed — this brand was removed at the request of its owner and is no longer extracted. The refusal costs neither a refresh window nor a quota unit.
500ApiError — internal_error — the request authenticated but the service failed to answer it.
Account
Confirm which organization the calling API key acts on.
GET/v1/me
Identify the calling key
Returns the organization the presented API key belongs to. Use it to confirm a key is live and to see which organization it acts on — there is no organization_id parameter anywhere in this API, the key determines the tenant.
Responses
200MeResponse — The organization the key belongs to.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
429RateLimitedError — rate_limited — the key exceeded its request window.
503ApiError — service_unavailable — the key verified but the organization could not be read. Retry rather than rotate the key.
Usage
What your organization has spent, and against what limit.
The one thing to understand before reading any figure here: quota meters answered calls, not the work behind them. A domain already held costs exactly what one extracted on demand costs, because the meter counts what we hand back rather than what it took to get. An explicit refresh costs more than a read. An organization sitting at its ceiling is refused rather than served.
GET/v1/usage
Read this organization's usage
Reports what this organization has spent against its allowance in the current period, and the ceiling actually being enforced. The figures count ANSWERED CALLS: a domain already held costs the same as one nobody has read yet, and an explicit refresh costs more than either because it always forces a fresh extraction. An organization sitting at its ceiling is refused rather than served. There is no organization parameter: the key decides the tenant. An organization that has never called anything reads as a zeroed standing rather than a 404.
Responses
200UsageStanding — This organization's standing for the current period.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
429RateLimitedError — rate_limited — the key exceeded its request window.
503ApiError — service_unavailable — the key verified but the ledger could not be read. Retry rather than rotate the key.
Billing
Buy a plan, manage the subscription, and cap what you can be charged.
Checkout and the Billing Portal both answer with a short-lived Stripe-hosted URL rather than handling card details themselves — no payment information ever passes through this API. The portal manages an existing customer, so an organization that has never checked out has nothing to open and is told so rather than having an empty customer created for it.
PUT /v1/billing/ceiling is the spend guard: it caps billable extractions per period and is the only endpoint here that changes what you can be charged. Zero is a legal ceiling: it does not revoke your key, but every billable call is refused with 429 until the ceiling is raised or the period rolls.
PUT/v1/billing/ceiling
Set this organization's spend ceiling
Caps how many units this organization can be billed for in a period. Send a whole number to set the cap, or null to clear it and fall back to the plan's own allowance. Zero is legal and means no billable calls this period: every read is refused with 429 until the cap is raised or the period rolls. What you send is what gets stored: the clamp against the plan allowance is applied on read, so an organization that raises its plan later gets the ceiling it actually asked for rather than one truncated to the tier it was on at the time. There is no organization parameter: the key decides whose ceiling moves.
200Ceiling — The ceiling now in force. clamped says whether the plan allowance is what is actually binding.
400BillingError — invalid_ceiling — the body named no monthly_ceiling or carried something other than null or a whole number of zero or greater. Nothing was written.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
429RateLimitedError — rate_limited — the key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the ceiling could not be written. Retry rather than rotate the key.
POST/v1/billing/checkout
Start a checkout for a plan
Opens a Stripe-hosted Checkout session for this organization and returns its URL. The organization's Stripe customer is created on first use and reused thereafter. The plan must be one this API can currently sell — a tier whose price does not yet exist answers 503 plan_unavailable before any Stripe object is created. There is no organization parameter: the key decides who is billed. The session is presented in GBP for callers in the UK and EUR for callers in the euro area, and in USD otherwise; the currency is resolved from the calling connection's country and is not a request parameter. Anywhere outside those three, Stripe presents the USD amount converted to the buyer's local currency on the hosted page.
400BillingError — unknown_plan — the body carried no readable plan or named a tier that does not exist.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
429RateLimitedError — rate_limited — the key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the checkout could not be created. Retry rather than rotate the key.
POST/v1/billing/portal
Open the billing portal
Returns a Stripe-hosted Billing Portal URL for this organization's existing customer record, where a subscription can be changed, a payment method updated or an invoice read. An organization that has never checked out has no customer to manage and answers 409 rather than having one silently created for it — start a checkout first. There is no organization parameter: the key decides whose portal opens.
Responses
200PortalSession — Redirect the customer to portal_url.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
409BillingError — no_billing_account — this organization has never bought anything and has no Stripe customer to manage.
429RateLimitedError — rate_limited — the key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the portal session could not be created. Retry rather than rotate the key.
Webhooks
Stop polling. Register an HTTPS endpoint and every extraction your organization is waiting on is delivered to it when it finishes — including one another organization triggered and you joined, which is the common case on a shared cache.
The endpoints below manage the registry. The delivery itself is an outbound request from us to you, which OpenAPI's operation model cannot express, so its contract is stated here.
event_type is one of brand.extraction.completed, brand.extraction.failed or webhook.test. Switch on it and ignore anything you do not recognise — the list is additive.
brand.extraction.completed — data is exactly the 200 body of GET /v1/brands/{domain} minus its quota block. Hand it to the same parser.
brand.extraction.failed — data is { "object": "extraction_failure", "domain": …, "failure_reason": … }, carrying the same closed failure_reason vocabulary the API serves. A suppressed domain is delivered here with reason suppressed, so a subscriber is never left waiting on a brand that will not arrive.
webhook.test — data is { "object": "webhook_test", "message": … } and carries no brand. Only a test send produces it.
Quality is a field, never an event
A completed delivery reports how much of the brand the extraction actually came back with, in the same two fields the 200 body carries: quality_verdict — complete, partial or thin — and missing_fields, the field names that read did not find. A null verdict means the entry predates the field and was never assessed; read it as unknown, never as complete, and missing_fields is null with it. Both report what we did not find, never a claim that the company does not have one.
Quality and failure are two axes and never appear on one delivery: a completed event carries a verdict and no failure_reason, a failed event carries a failure_reason and no verdict. A thin extraction is therefore delivered as brand.extraction.completed with a thin verdict — there is no degraded event type, and none is coming. Alert on the field.
Verifying the signature
Every delivery carries x-extractbrand-signature: t=<unix seconds>,v1=<hex>. The digest is HMAC-SHA256(secret, "<t>.<raw request body>"), hex-encoded. Verify against the raw bytes, never a re-serialization of the parsed JSON, and compare in constant time. Reject a delivery whose t is more than 300 seconds from your clock — the timestamp is inside the signed material precisely so that check is yours to make.
Other headers: x-extractbrand-event-id, x-extractbrand-event-type and x-extractbrand-delivery-attempt. All three are also inside the signed body.
Delivery and retries
At-least-once. Answer 2xx as soon as you have durably accepted the event and do your work afterwards; anything else is a failure. A 429 or 5xx is retried up to five attempts total with 30s / 2m / 8m / 32m backoff, and every other 4xx is not retried at all — those describe the request, and repeating it changes nothing. Redirects are never followed. **Deduplicate on event_id**: it is stable across every retry of one delivery and distinct for every endpoint.
A slow or failing receiver never affects the extraction itself, and deliveries are never metered.
GET/v1/webhooks
List webhook endpoints
Every webhook endpoint this organization has registered along with the outcome of the last delivery attempted against each one. Signing secrets are never returned by this endpoint or by any other read — the secret is shown once when the endpoint is created.
Responses
200WebhookEndpointList — The organization's endpoints. The list is empty when none are registered.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
429RateLimitedError — rate_limited — the API key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the registry could not be read.
POST/v1/webhooks
Register a webhook endpoint
Registers an HTTPS URL that receives brand.extraction.completed and brand.extraction.failed deliveries for every extraction this organization is waiting on — including one another organization triggered and this one joined. The response carries signing_secret and it is the only time that value is ever returned. The URL must be HTTPS and its hostname must not be a loopback, private, link-local or cloud-metadata address; the hostname is checked as written and is never resolved, and the same check runs again immediately before every delivery. An organization may register up to five endpoints.
201WebhookEndpointCreated — The endpoint was registered. Store signing_secret now — no later read returns it.
400WebhookUrlRejectedError — invalid_webhook_url when the URL broke one of the target rules — error.reason names which. invalid_parameter when the body carried no url string.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
409ApiError — webhook_url_registered when this organization already registered that URL. webhook_limit_reached when it already holds the maximum number of endpoints.
429RateLimitedError — rate_limited — the API key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the registry could not be written.
DELETE/v1/webhooks/{endpoint_id}
Delete a webhook endpoint
Stops deliveries to an endpoint. The endpoint becomes disabled rather than disappearing so its delivery history stays readable and the change is reversible by registering the URL again. A delivery already queued for this endpoint is dropped rather than sent — the target is re-read at delivery time. An endpoint id that belongs to another organization answers 404 exactly as an unknown one does.
Parameters
endpoint_idpath · required — The endpoint's own id, as returned when it was registered.
Responses
200WebhookEndpoint — The endpoint is disabled and will receive no further deliveries.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
404ApiError — not_found — this organization has no endpoint with that id.
429RateLimitedError — rate_limited — the API key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the registry could not be written.
POST/v1/webhooks/{endpoint_id}/test
Send a test delivery
Sends one signed webhook.test delivery to a registered endpoint and answers with what the receiver did. The payload carries no brand data by design — a probe that arrived as a real extraction event would push invented data through your production handler. Exactly one attempt is made and it is never retried; the attempt is recorded in the endpoint's delivery history alongside its real deliveries.
Parameters
endpoint_idpath · required — The endpoint's own id, as returned when it was registered.
Responses
200WebhookTestResult — The attempt was made. delivered reports whether the receiver answered 2xx.
401ApiError — invalid_api_key — the Authorization header is missing or malformed or names a key that does not verify.
404ApiError — not_found — this organization has no active endpoint with that id.
429RateLimitedError — rate_limited — the API key exceeded its request window.
503ApiError — service_unavailable — the request authenticated but the registry could not be read.
Response and request shapes
Every shape below is generated from the Zod schemas the runtime validates against.
positioning_statementstring | nullrequired — Why this company exists, who it serves, and what makes it different.
valuesstring[]required
colorsobject[]required — Every colour of the brand, as one ranked list, deduplicated by hex. Read colors[0] for the dominant colour, or find the entry whose role you want — match on role rather than assuming a position, because a brand missing a role shifts every entry below it up. The entries with a non-null role are the brand's paint kit, in rank order; the entries with role: null are colours the page genuinely paints that fill no role, ranked after them by painted area. A colour holds one role only: a brand whose body ink is also its primary reports that hex once, under the higher-ranked role.
hexstringrequired — The colour as #rrggbb, lowercase.
role"primary" | "secondary" | "accent" | "background" | "surface" | "text"required — What the colour is for, or null for a colour the page paints that fills no role. The order of this list is the rank order.
onstringrequired — A text colour that is legible on hex, so a caller never derives one.
namesstring[]required — The names the site's own stylesheet gives this colour — its CSS custom properties. Empty when the site names it nothing.
coveragenumber | nullrequired — Fraction of the rendered page this colour paints, 0 to 1. Null when the page could not be rendered or the entry predates this measurement.
fontsobject[]required — The brand's faces, most prominent first, at most two. Replaces the former array of family names and the typography block: family is what that array carried, role is the heading/body split and availability is how to load the face.
familystringrequired — The family name as the site declares it.
role"display" | "body"required — Where the face is used. The first family is the display face.
availability"google" | "custom-webfont" | "system" | "unknown"required — How the face can be loaded. unknown when the site gave no signal.
font_confidencenumberrequired — How firmly the families above were resolved, 0 to 1.
motionBrandMotion | nullrequired — Null when the site carries no motion evidence.
spacingobjectrequired — The spacing system read off the brand's own CSS.
base_unitnumberrequired — The base spacing unit in pixels.
scalenumber[]required — The scale, as multipliers of base_unit.
content_marginnumberrequired — The outer horizontal content margin in pixels.
accessibilityobjectrequired
contrast_target"AA" | "AAA"required
min_font_size_pxnumberrequired
colorblind_safebooleanrequired
tonestring[]required
voicestring[]required
copywritingobjectrequired
preferred_termsstring[]required — Words and phrases the brand actively uses.
avoided_termsstring[]required — Words and phrases that conflict with the voice.
cta_stylestringrequired — How calls to action are written on this brand.
headline_casestringrequired — Common values: title, sentence, upper, mixed.
tone_by_contextobject[]required — How the voice shifts across surfaces. Empty when the tone is uniform.
contextstringrequired
tonestringrequired
brand_intelligenceobject | nullrequired — A deeper read of the company behind the brand. Null when the site gave too little to work from.
company_summarystringrequired
known_forstring[]required
design_personalitystringrequired
design_conventionsstring[]required
slide_guidancestringrequired
brand_valuesstring[]required
inferred_audiencestringrequired
writing_stylestringrequired
confidencenumberrequired — Overall extraction confidence, 0 to 1. Low values are reported, never hidden.
confidence_per_fieldRecord<string, number>required — Per-field confidence, keyed by extracted field name — primary, fonts, positioning_statement and the like. A field the extraction never reached is absent rather than zero.
confidence_signalsstring[]required — What the confidence score was built from.
gapsstring[]required — Fields no specialist could fill. Empty means the extraction was complete.
screenshot_blob_urlstring | nullrequired — The full-page screenshot the brand was read from. Null when the page was read without rendering.
schema_versionstringrequired — The payload version. Bumped only when a field changes meaning.
BrandLogo
urlstring | nullrequired — The primary mark, at the URL the site itself serves it from.
svgstring | nullrequired — The mark as a sanitized SVG document, when the site publishes it inline rather than as a file — the only form of the mark when url is null and source is inline-svg. Render it as an image (for example via a data: URI), never as live markup.
on_dark_urlstring | nullrequired — The variant to place on a dark background, when the site publishes a distinct one.
on_light_urlstring | nullrequired — The variant to place on a light background, when the site publishes a distinct one.
sourcestring | nullrequired — Where the mark was found. Common values: inline-svg, header-img, json-ld, apple-touch-icon, og-image, manifest, favicon.
descriptionstring | nullrequired — One or two sentences on the mark's form and what it depicts, read from the rendered page.
dominant_colorsstring[]required — Hex colours sampled from the mark itself.
luminancenumber | nullrequired — Perceived lightness of the mark, 0 (black) to 1 (white).
has_transparencybooleanrequired — Whether the mark carries an alpha channel.
contrast_profile"light-dominant" | "dark-dominant" | "mixed" | "colorful"required — Which backgrounds the mark reads against.
background_colorstring | nullrequired — The colour the mark is drawn on where it appears baked into a plate.
BrandMotion
personalitystring | nullrequired — One sentence on how the brand visibly animates.
intensitystring | nullrequired — Common values: none, minimal, subtle, moderate, rich. Use a richer phrase when it better captures the brand.
speedstring | nullrequired — Common values: instant, fast, moderate, slow. Use a richer phrase when it better captures the brand.
easing_stylestring | nullrequired — The character of the observed timing functions.
signature_effectsstring[]required — Recognisable recurring effects, by element and behaviour.
detected_librariesstring[]required
has_scroll_animationsboolean | nullrequired
has_hover_effectsboolean | nullrequired
reduced_motion_supportboolean | nullrequired
BrandProvenance
source"measured" | "owner"required — Whether any field in this response was authored by the brand's owner. 'owner' when owner_fields is non-empty, 'measured' otherwise.
owner_fieldsstring[]required — Dotted paths into this response that the brand's verified owner authored. Every field not listed was measured from the brand's own site.
corner_stylestring | nullrequired — Common values: sharp, subtle, rounded, pill. Use a richer phrase when it better captures the brand.
headline_transformstring | nullrequired — Common values: uppercase, capitalize, none. Use a richer phrase when it better captures the brand.
headline_trackingstring | nullrequired — Common values: tight, normal, wide. Use a richer phrase when it better captures the brand.
headline_weightstring | nullrequired — Common values: light, normal, medium, bold, heavy. Use a richer phrase when it better captures the brand.
shadow_stylestring | nullrequired — Common values: none, subtle, elevated, dramatic. Use a richer phrase when it better captures the brand.
uses_gradientsboolean | nullrequired
border_stylestring | nullrequired — Common values: none, subtle, prominent. Use a richer phrase when it better captures the brand.
photography_treatmentstring | nullrequired — Common values: full-color, muted, grayscale, duotone, high-contrast. Use a richer phrase when it better captures the brand.
densitystring | nullrequired — Common values: dense, balanced, spacious. Use a richer phrase when it better captures the brand.
aesthetic_tagsstring[]required
headline_letter_spacingstring | nullrequired — The authored CSS value, e.g. -0.02em.
headline_font_weightnumber | nullrequired
border_radiusstring | nullrequired — The authored CSS value, e.g. 12px.
box_shadowstring | nullrequired — The authored CSS value.
icon_stylestring | nullrequired — Common values: outlined, filled, duotone, hand-drawn. Use a richer phrase when it better captures the brand.
illustration_stylestring | nullrequired — Common values: geometric, organic, flat, 3d, isometric, hand-drawn, line-art, none. Use a richer phrase when it better captures the brand.
photography_subjectsstring | nullrequired — What the brand's imagery actually shows.
photography_compositionstring | nullrequired — How that imagery is composed and cropped.
photography_color_gradingstring | nullrequired — The colour treatment applied to it.
patternsstring[]required — Recurring graphic motifs, e.g. faint grid lines.
Ceiling
ceilingnumberrequired — The limit now in force, after clamping against the plan allowance.
ceiling_source"plan" | "org"required — Which of the two set the figure above.
clampedbooleanrequired — True when the stored value was higher than the plan allows and the plan's number is what is being enforced. The value you sent is still what is stored.
CeilingRequest
monthly_ceilingnumber | nullrequired — A whole number of extractions per period, or null to clear the ceiling and fall back to the plan's allowance. Zero is legal and means no fresh extractions this period. The value you send is what gets stored — the clamp against the plan allowance is applied on read, so raising your plan later restores the ceiling you actually asked for.
CheckoutRequest
plan"free" | "starter"required — The plan to purchase.
CheckoutSession
checkout_urlstringrequired — A Stripe-hosted Checkout URL. Redirect the customer to it; it is single-use and expires.
messagestringrequired — A human-readable sentence describing the failure.
retry_after_secondsnumberoptional — Whole seconds to wait before retrying. Mirrors the Retry-After header.
ServedCacheStatusSchema
"hit" | "stale"
UsagePeriod
keystringrequired — The period's own key.
startstringrequired — ISO-8601 UTC. Inclusive.
endstringrequired — ISO-8601 UTC. Exclusive.
UsageStanding
organization_idstringrequired — The organization the calling key acts on. There is no organization parameter anywhere in this API — the key decides the tenant.
plan"free" | "starter"required — The plan this organization is currently on.
fresh_reads_usednumberrequired — Extractions debited this period. Cached reads are never counted.
fresh_reads_limitnumberrequired — The effective allowance — the same number ceiling reports.
fresh_reads_remainingnumberrequired — fresh_reads_limit minus fresh_reads_used, floored at zero.
ceilingnumberrequired — The limit actually enforced on extraction. Identical to fresh_reads_limit; both are reported so a client need not infer that they are the same figure.
ceiling_source"plan" | "org"required — plan when the tier's own allowance is binding, org when this organization set a lower ceiling of its own via PUT /v1/billing/ceiling.
WebhookEndpoint
object"webhook_endpoint"required
endpoint_idstringrequired
urlstringrequired
status"active" | "disabled"required
descriptionstringoptional
created_atstringrequired — ISO-8601 UTC.
disabled_atstringoptional — ISO-8601 UTC. Present only while the endpoint is disabled.
signing_secretstringrequired — The HMAC-SHA256 key this endpoint's deliveries are signed with. Returned exactly once — here — and never by any later read.
WebhookEndpointList
object"list"required
dataWebhookEndpoint[]required
WebhookLastAttempt
outcome"delivered" | "failed"required
attemptnumberrequired — Which attempt of that delivery this was, 1-based.
response_statusnumber | nullrequired — The receiver's HTTP status, or null when nothing answered.
error_kind"http_status" | "network" | "timeout" | "blocked_url"optional — Why the attempt failed. Absent on a delivered attempt.
duration_msnumberrequired
occurred_atstringrequired — ISO-8601 UTC.
WebhookRegisterRequest
urlstringrequired — The HTTPS URL deliveries are POSTed to. It is validated against the platform's SSRF guard both now and immediately before every delivery, so a host that later resolves to a private address stops being delivered to.
descriptionstringoptional — Your own label for this endpoint. Never interpreted.
WebhookTestResult
object"webhook_test"required
endpoint_idstringrequired
event_idstringrequired — The event_id inside the payload that was sent.
deliveredbooleanrequired — True when the receiver answered 2xx. A test send is never retried.
response_statusnumber | nullrequired — The receiver's HTTP status, or null when nothing answered.
error_kind"http_status" | "network" | "timeout" | "blocked_url"optional — Why it failed. Absent when it was delivered.