{"openapi":"3.1.0","info":{"title":"Placement API","version":"1.0.0","description":"Normalised backlink placement inventory, deduplicated across multiple suppliers, with independent verification, held quotes, ordering, content handling and post-placement outcome tracking. Every price is the buyer price in credits (1 credit = 1 US cent), already normalised from supplier currencies, so the figure you plan with is the figure you are charged. Supplier identity is never exposed.\n\nAuthenticate with a `pk_live_…` API key as a Bearer token. Keys carry scopes: `read` (catalogue, quotes, account reads) and `order` (anything that moves money). An MCP server exposing the same operations as tools is available at https://api.placementapi.com/mcp with the same Bearer key; the tools call these endpoints, so pricing and verification are identical either way. Unknown query parameters and brief fields are refused with 400 rather than ignored.","contact":{"email":"hello@placementapi.com"},"termsOfService":"https://placementapi.com/terms"},"servers":[{"url":"https://api.placementapi.com"}],"security":[{"apiKey":[]}],"tags":[{"name":"Catalogue","description":"Search inventory, inspect a publisher, read market context."},{"name":"Planning","description":"Briefs in, costed and held baskets out."},{"name":"Orders","description":"Place, list, cancel and follow orders. Requires the `order` scope to spend."},{"name":"Content","description":"Articles for placements where the buyer supplies or commissions copy."},{"name":"Account","description":"Balance, spend limits, keys, briefs and automation."},{"name":"Checkout","description":"Card checkout for a basket, with or without an account."},{"name":"Research","description":"Target profiles, gap maps and keyword winners (DataForSEO-backed)."},{"name":"Meta","description":"Service banner, health and field semantics."}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"Customer API key (`pk_live_…`) in `Authorization: Bearer <key>`. Issue from POST /v1/keys or the dashboard. Endpoints marked session-only reject API keys."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string"},"description":"Required. A retry without one is indistinguishable from a second purchase and is refused (400 idempotency_key_required) rather than double-charged. A replay with the same key returns the original result with `replayed: true`."}},"responses":{"BadRequest":{"description":"Malformed request. `error` is `bad_request` (with a zod `detail`), `invalid_json`, or `unknown_parameter` / `unknown_field` (with `unknown[]` and `accepted[]`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed or revoked Bearer credential: `{ error: \"unauthorized\" }`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"`insufficient_scope` when the key lacks the `order` scope; `customer_key_required` when the operator token is used where a customer must be attributable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found. Also returned for resources belonging to another customer, so an id is not confirmed as real.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"State conflict; see the operation for the `error` codes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limited. 1,000 requests and 20,000 returned rows per key per hour, a monthly row entitlement that grows with credits purchased, and a maximum offset of 10,000. Carries `Retry-After` and `X-RateLimit-*` / `X-Quota-Rows-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"`server_misconfigured` when the store is not configured; `upstream_unfunded` when the measurement provider's prepaid balance is exhausted (fresh profiles, gap maps and SERPs pause; cached results and catalogue are unaffected); `upstream_unavailable` on a provider error; `payments_unavailable` when card payments are not configured.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Machine-readable code, snake_case."},"detail":{"description":"Human-readable explanation, or a zod flatten() object on validation failures."}},"additionalProperties":true},"InventoryMode":{"type":"string","enum":["named","pre_approval","blind"],"description":"The most important field. 'named' = a specific, identified domain you can inspect before ordering. 'pre_approval' = the vendor proposes candidate domains and the buyer approves them BEFORE placement. 'blind' = a promise (e.g. DR50+, finance) where the domain is assigned at fulfilment with no veto. Only 'named' carries a site_id. Never present a pre_approval or blind offer as though a specific site were secured, and never price-compare a specification against a named domain as the same product."},"PlacementType":{"type":"string","enum":["guest_post","niche_edit","link_insert","homepage_link","media_placement"]},"OrderStatus":{"type":"string","enum":["ordered","awaiting_content","content_revision_requested","awaiting_operator","submitted_to_vendor","delivered","cancelled","refunded","failed"]},"Verification":{"type":["string","null"],"enum":["ok","suspect","unverified",null],"description":"Our independent verdict on the claimed authority; no supplier publishes it. 'suspect' = high link authority with little or no organic search presence, which is what a link farm looks like — do not plan around suspect domains. 'ok' = measured, and search presence matches the rating. 'unverified' = not measured yet, which is not a pass."},"QualityReasons":{"type":"array","items":{"type":"string"},"description":"Which rules made verification read suspect: high_authority_no_traffic, high_authority_no_keywords, spam_score, subdomain_without_own_footprint, saturated_outbound. Empty when not suspect."},"Restricted":{"type":"array","items":{"type":"string"},"description":"gambling / adult / piracy when the site is one, from the domain name and a short topic list. Such rows appear only when asked for (include_restricted, or a topic/accepts naming the vertical)."},"Freshness":{"type":"object","description":"Prices are scrapes, not live quotes. Age travels with the data; stale after 48h.","properties":{"observed_at":{"type":"string","format":"date-time"},"age_hours":{"type":"number"},"stale":{"type":"boolean"},"stale_after_hours":{"type":"integer","const":48}}},"Plausibility":{"type":["object","null"],"description":"Where this price sits among comparable domains (same placement type, inventory mode and claimed-DR decade). flag 'low' = under the 10th percentile, which is bought authority more often than it is a bargain; 'high' = above the 90th. Null when the band had too few domains to describe.","properties":{"pct":{"type":"number","minimum":0,"maximum":100},"band":{"type":"string","examples":["guest_post named DR 50-59"]},"flag":{"type":"string","enum":["low","ok","high"]},"n":{"type":"integer","description":"Domains behind the distribution."}}},"PlacementScore":{"type":["object","null"],"description":"Placement Score v1: { score 0-100, version, confidence high|medium|low, components each 0-100 or null, capped }. Weights (authority 25 / visibility 25 / quality 30 / graph 10 / survival 10) are stated, not fitted; unmeasured components drop out and the rest re-weight; a suspect publisher never scores above 40. Derived from the fields beside it, so it can be checked. Does NOT include observed effectiveness, relevance to your brief, or supplier reliability. Quote the components, not just the number.","properties":{"score":{"type":"integer","minimum":0,"maximum":100},"version":{"type":"string"},"confidence":{"type":"string","enum":["high","medium","low"],"description":"Share of the weight that had a measurement behind it."},"components":{"type":"object","properties":{"authority":{"type":["number","null"]},"visibility":{"type":["number","null"]},"quality":{"type":["number","null"]},"graph":{"type":["number","null"]},"survival":{"type":["number","null"]}}},"capped":{"type":["string","null"],"enum":["suspect",null]}}},"MarketReference":{"type":["object","null"],"description":"The median ASK of comparable placements (same type, inventory mode and claimed-DR decade), not a clearing price — say 'market reference', never 'fair value'. own_asks is the spread of asks across suppliers for this exact domain and type, as counts and credits only. ask_history is null under 14 days of observations. Null on a thin band.","properties":{"reference_credits":{"type":"integer"},"method":{"type":"string","const":"band_median_v1"},"comparables_n":{"type":"integer"},"vs_reference_pct":{"type":"number","description":"This price against the reference, in percent; negative is cheaper."},"assessment":{"type":"string","enum":["under","at","over"],"description":"±15% band around the reference."},"own_asks":{"type":"object","properties":{"listings":{"type":"integer"},"suppliers":{"type":"integer"},"lowest":{"type":"integer"},"median":{"type":"integer"},"highest":{"type":"integer"}}},"ask_history":{"type":["object","null"],"properties":{"first_seen_at":{"type":"string","format":"date-time"},"days_observed":{"type":"integer"},"first_credits":{"type":"integer"},"change_pct":{"type":"number"}}}}},"SupplierReliability":{"type":"object","description":"How the seller behind the line has performed for us (fulfilment, on-time delivery, link survival, failures, refunds) over n submitted orders. Never a name. Within 5% of price the basket prefers the better band; 'unknown' (under five orders) is judged on price alone.","properties":{"band":{"type":"string","enum":["high","medium","low","unknown"]},"n":{"type":"integer"}}},"Survival":{"type":["object","null"],"description":"What happened to the placements WE bought on this publisher. Our crawler re-checks every delivered link at 0/7/30/60/90/180/365 days. Null under three orders.","properties":{"orders":{"type":"integer"},"checks":{"type":"integer"},"live_share":{"type":"number"},"live_90d_share":{"type":["number","null"]},"median_days_to_missing":{"type":["number","null"]},"dofollow_share":{"type":["number","null"]},"noindex_share":{"type":["number","null"]},"updated_at":{"type":"string","format":"date-time"}}},"Demand":{"type":["object","null"],"description":"Observed demand: distinct buyers who bought the domain in twelve months and the niches they bought for. Null under the three-buyer floor. Nothing about any one buyer is ever here.","properties":{"buyers_12m":{"type":"integer"},"purchases_12m":{"type":"integer"},"niches":{"type":"array","items":{"type":"object","properties":{"topic":{"type":"string"},"buyers":{"type":"integer"}}}}}},"LeaderLinks":{"type":["object","null"],"description":"Common Crawl web graph, root sites only (null on subdomains): how many of a topic's top-ranking sites the domain links to, keyed by topic.","additionalProperties":{"type":"object","properties":{"linked":{"type":"integer"},"of":{"type":"integer"}}}},"ContentSpec":{"type":["object","null"],"description":"The brief to write against when the buyer supplies copy. Null when copy cannot be supplied.","properties":{"word_count_min":{"type":["integer","null"]},"word_count_max":{"type":["integer","null"]},"links_allowed":{"type":"integer"},"anchor_types_forbidden":{"type":"array","items":{"type":"string"}},"rules":{"type":"array","items":{"type":"string"},"description":"Editorial rules in the publisher's own words."}}},"ContentOption":{"type":"object","description":"A writing tier purchasable with the placement. Tiers are normalised across every supplier: 'supplied' = you provide the copy; 'included' = writing is in the price; 'standard' = purchasable, AI-drafted and human-edited; 'native' = purchasable, native expert writer; 'papi_written' = Placement Papi drafts to the spec for a flat 1,500 credits charged with the order, buyer approves before it goes out.","properties":{"tier":{"type":"string","enum":["supplied","included","standard","native","papi_written"]},"vendor_label":{"type":["string","null"],"description":"Pass as content_tier for content_mode 'service'."},"price_credits":{"type":"integer","description":"Add-on cost. Credits. 1 credit = 1 US cent."},"word_count":{"type":["integer","null"]},"note":{"type":["string","null"]}}},"OrderRequirements":{"type":["object","null"],"description":"What you MUST send, what you MAY send, and what you can buy, normalised across suppliers. `required` lists the fields to include in POST /v1/orders. Read `confidence`: 'observed' and 'documented' are reliable; 'assumed' means the confirmation step may ask for something not listed.","properties":{"required":{"type":"array","items":{"type":"string"},"description":"Always includes target_url; anchor_text is absent only where the publisher writes the anchor."},"optional":{"type":"array","items":{"type":"string"}},"content":{"type":"object","properties":{"supplied_allowed":{"type":"boolean","description":"Whether you may write the article yourself."},"spec":{"$ref":"#/components/schemas/ContentSpec"},"options":{"type":"array","items":{"$ref":"#/components/schemas/ContentOption"}},"due_days":{"type":["integer","null"],"description":"Days a supplied article may take to arrive after ordering (14); null where the publisher writes."}}},"speed":{"type":"object","properties":{"standard_days":{"type":["integer","null"]},"expedited_days":{"type":["integer","null"]},"expedited_surcharge_bps":{"type":["integer","null"]}}},"confirmation_required":{"type":"boolean","description":"True when a human at the supplier confirms the line before it is placed; treat the order as pending rather than done."},"rules":{"type":"array","items":{"type":"string"},"description":"Placement rules the buyer must respect regardless of who writes the copy."},"confidence":{"type":"string","enum":["observed","documented","assumed"]}}},"AnchorPlan":{"type":"object","description":"Anchor text is a DISTRIBUTION, not a string. Many links with one exact-match anchor is the classic over-optimisation footprint. Mix branded, topical, generic and naked_url anchors.","required":["anchors"],"properties":{"anchors":{"type":"array","minItems":1,"items":{"type":"object","required":["text","type"],"properties":{"text":{"type":"string","minLength":1,"maxLength":300},"type":{"type":"string","enum":["branded","naked_url","generic","topical","partial_match","exact_match"]},"weight":{"type":"number","exclusiveMinimum":0,"default":1,"description":"Relative weight; normalised across the plan."}}}}}},"ContentBody":{"type":"object","description":"An article. Send body_markdown, body_html or doc_url. A doc_url alone cannot be validated and is accepted with a warning.","properties":{"title":{"type":["string","null"],"maxLength":300},"body_markdown":{"type":["string","null"],"maxLength":200000},"body_html":{"type":["string","null"],"maxLength":400000},"doc_url":{"type":["string","null"],"format":"uri","maxLength":2000},"meta_description":{"type":["string","null"],"maxLength":400}}},"ContentValidation":{"type":"object","description":"Will this pass the publisher? Word count against the spec, external links against the allowance, the target linked exactly once with the anchor the order names, no forbidden anchor type.","properties":{"ok":{"type":"boolean"},"word_count":{"type":["integer","null"]},"links_found":{"type":"integer"},"target_link_found":{"type":"boolean"},"problems":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}},"OfferMetrics":{"type":["object","null"],"description":"What we know about the domain, and who says so. Claimed and measured figures are never merged: where they disagree, the measured one is the one to trust. Null when nothing is known.","properties":{"dr":{"type":["number","null"],"description":"Supplier-claimed Ahrefs-style score, 0-100; the scale dr_min filters on. On a subdomain this is the PARENT's number."},"traffic":{"type":["number","null"],"description":"Verified traffic where measured, else the claim."},"source":{"type":"string","description":"Provenance label for dr/traffic. Measured figures are in the verified_* fields."},"verification":{"$ref":"#/components/schemas/Verification"},"quality_reasons":{"$ref":"#/components/schemas/QualityReasons"},"subdomain_of":{"type":["string","null"],"description":"Parent domain when the host is a subdomain (blog.example.com, anything.blogspot.com). A subdomain is listed at its parent's DR and inherits none of it: read verified_dr, verified_referring_domains and verified_traffic, which are this host's own."},"restricted":{"$ref":"#/components/schemas/Restricted"},"outbound_external":{"type":["integer","null"],"description":"Outside domains linked to (Common Crawl; root sites only)."},"inbound_domains":{"type":["integer","null"]},"cc_pagerank":{"type":["number","null"]},"leader_links":{"$ref":"#/components/schemas/LeaderLinks"},"survival":{"$ref":"#/components/schemas/Survival"},"placement_score":{"$ref":"#/components/schemas/PlacementScore"},"verified_organic_keywords":{"type":["integer","null"],"description":"OUR independent measurement."},"verified_spam_score":{"type":["number","null"]},"as_of":{"type":["string","null"],"format":"date-time"},"verified_traffic":{"type":["number","null"],"description":"Independently measured monthly traffic."},"verified_dr":{"type":["number","null"],"description":"DataForSEO backlinks rank, 0-1000. Deliberately NOT rescaled to look like a DR."},"verified_referring_domains":{"type":["integer","null"]}}},"Offer":{"type":"object","description":"One catalogue row from GET /v1/offers. Supplier identity is intentionally not exposed anywhere in this API. You are buying from us; which upstream marketplace fulfils a placement is not something you need or can act on.","properties":{"offer_id":{"type":"string","description":"Public offer id. Its prefix is the opaque supplier token accepted by the `supplier` filter."},"placement_type":{"$ref":"#/components/schemas/PlacementType"},"inventory_mode":{"$ref":"#/components/schemas/InventoryMode"},"site_id":{"type":["string","null"],"description":"Canonical domain. Only 'named' inventory carries one."},"price":{"type":"object","description":"The BUYER price: the figure debited at order time, so a basket costed from this endpoint will not move at checkout.","properties":{"credits":{"type":"integer","description":"Credits. 1 credit = 1 US cent."},"usd_minor":{"type":"integer","description":"The same number in cents, so you never have to convert."},"currency":{"type":"string","const":"USD"},"as_of":{"type":"string","format":"date-time"}}},"plausibility":{"$ref":"#/components/schemas/Plausibility"},"demand":{"$ref":"#/components/schemas/Demand"},"bulk_breaks":{"description":"Supplier volume breaks, as recorded."},"link_attrs":{"description":"Link attributes as recorded (e.g. links_per_placement, dofollow)."},"content":{"description":"Raw content facts on the listing (e.g. included, word_count_min, author). Prefer order_requirements.content."},"guarantees":{"description":"Vendor guarantees on blind/pre_approval tiers (e.g. dr_min, referring_domains_min, traffic_min)."},"metrics":{"$ref":"#/components/schemas/OfferMetrics"},"turnaround_days_est":{"type":["integer","null"]},"incentive":{"type":["object","null"],"description":"A discount for ordering before the next batch closes. Rewards WHEN you order, never how much; spend caps still apply.","properties":{"discount_pct":{"type":"number"},"price_with_incentive_credits":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"reason":{"type":"string"}}},"order_requirements":{"$ref":"#/components/schemas/OrderRequirements"},"niches_allowed":{"type":"array","items":{"type":"string"},"description":"The supplier's own raw labels. Prefer topics."},"topics":{"type":"array","items":{"type":"string"},"description":"Harmonised subjects."},"accepts":{"type":"array","items":{"type":"string"},"description":"Empty means UNKNOWN, not 'accepts nothing'."},"geo":{"description":"Country, returned per offer; there is no geo filter on GET /v1/offers, filter client-side."},"languages":{},"publisher_notes":{"type":["string","null"]},"observed_at":{"type":"string","format":"date-time"},"age_hours":{"type":"number"},"stale":{"type":"boolean"},"stale_after_hours":{"type":"integer"}}},"QuoteAlternative":{"type":"object","description":"Another domain that could take this line's place, HELD at the price shown. Order it with the same quote_id and it is charged that price; the primary it replaces is simply not ordered.","properties":{"offer_id":{"type":"string"},"site_id":{"type":["string","null"]},"placement_type":{"$ref":"#/components/schemas/PlacementType"},"price_credits":{"type":"integer"},"dr":{"type":["number","null"]},"verification":{"$ref":"#/components/schemas/Verification"},"plausibility_pct":{"type":["number","null"]}}},"QuoteLine":{"type":"object","description":"One placement in a held basket (POST /v1/quote, POST /v1/targets/gap, GET /v1/quotes/{quote_id}). Fields are added, never renamed.","properties":{"offer_id":{"type":"string"},"site_id":{"type":["string","null"],"description":"Null on blind inventory."},"inventory_mode":{"$ref":"#/components/schemas/InventoryMode"},"placement_type":{"$ref":"#/components/schemas/PlacementType"},"price_credits":{"type":"integer","description":"Held price. Credits. 1 credit = 1 US cent."},"dr":{"type":["number","null"],"description":"The vendor's claim. On a subdomain, the parent's number."},"traffic":{"type":["number","null"]},"verification":{"$ref":"#/components/schemas/Verification"},"verified_dr":{"type":["number","null"],"description":"DataForSEO backlinks rank, 0-1000; corroborates, does not replace dr."},"verified_traffic":{"type":["number","null"]},"organic_keywords":{"type":["integer","null"]},"referring_domains":{"type":["integer","null"]},"spam_score":{"type":["number","null"]},"subdomain_of":{"type":["string","null"]},"quality_reasons":{"$ref":"#/components/schemas/QualityReasons"},"restricted":{"$ref":"#/components/schemas/Restricted"},"outbound_external":{"type":["integer","null"]},"inbound_domains":{"type":["integer","null"]},"cc_pagerank":{"type":["number","null"]},"leader_links":{"$ref":"#/components/schemas/LeaderLinks"},"survival":{"$ref":"#/components/schemas/Survival"},"supplier_reliability":{"$ref":"#/components/schemas/SupplierReliability"},"placement_score":{"$ref":"#/components/schemas/PlacementScore"},"market_reference":{"$ref":"#/components/schemas/MarketReference"},"plausibility":{"$ref":"#/components/schemas/Plausibility"},"demand":{"$ref":"#/components/schemas/Demand"},"why":{"type":"array","items":{"type":"string"},"description":"Factual sentences behind the pick, derived from the fields on this line and the brief — cite them. Prose; do not parse it."},"relevance":{"description":"Topic coverage of the brief, or null when the brief named no topics."},"alternatives":{"type":"array","maxItems":2,"items":{"$ref":"#/components/schemas/QuoteAlternative"}},"turnaround_days":{"type":["integer","null"]},"topics":{"type":"array","items":{"type":"string"}},"guarantees":{},"order_requirements":{"$ref":"#/components/schemas/OrderRequirements"}}},"Quote":{"type":"object","description":"A costed, HELD basket. Nothing is reserved and nothing is charged until you order, so quoting is free. Prices are held for ttl_seconds (4 hours).","properties":{"quote_id":{"type":"string"},"quote_token":{"type":"string","description":"Bearer for GET /v1/quotes/{quote_id}?token=… so an approval link can show the plan without a key. Dies with the quote."},"view_url":{"type":"string","format":"uri"},"target_url":{"type":["string","null"]},"expires_at":{"type":"string","format":"date-time"},"ttl_seconds":{"type":"integer","const":14400},"binding":{"type":"boolean","const":true,"description":"These prices are ours to honour until they lapse, even if the catalogue moves."},"strategy":{"type":"string","enum":["cheapest","fastest","safest"]},"requested":{"type":"integer"},"filled":{"type":"integer"},"eligible":{"type":"integer","description":"Distinct options that matched before the basket was capped."},"excluded":{"type":"object","additionalProperties":{"type":"integer"},"description":"Candidate offers each quality gate removed, keyed by reason (e.g. suspect, restricted, already_linking, bought_for_target). Pre-collapse counts; do not sum with eligible."},"total_credits":{"type":"integer","description":"Credits. 1 credit = 1 US cent."},"at_median_supplier_credits":{"type":"integer","description":"The same basket priced at the median supplier for each domain."},"saving_vs_median_credits":{"type":"integer"},"funding":{"type":["object","null"],"properties":{"balance_credits":{"type":"integer"},"sufficient":{"type":"boolean"},"shortfall_credits":{"type":"integer"}}},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"profile_hint":{"type":"object","description":"Only when exclude_domains_linking_to was given: the target's profile bands beside the basket's.","properties":{"target_domain":{"type":"string"},"referring_domains_fetched":{"type":"integer"},"profile_rank_bands_share":{"type":"object"},"profile_rank_bands_basis":{"type":"string"},"carried_referring_domains":{"type":"integer"},"profile_dr_bands_share":{"type":"object"},"profile_dr_bands_basis":{"type":"string"},"profile_heaviest_dr_band":{},"basket_dr_bands":{"type":"object"},"dr_band_suggested":{}}},"next":{"type":"object","properties":{"order":{"type":"string"},"note":{"type":"string"}}}}},"Brief":{"type":"object","description":"A plan_links brief. Unknown fields are refused (400 unknown_field), not ignored: a quote built from a partly understood brief would name domains you did not choose.","required":["links"],"additionalProperties":false,"properties":{"links":{"type":"integer","minimum":1,"maximum":100,"description":"How many placements to plan."},"strategy":{"type":"string","enum":["cheapest","fastest","safest"],"default":"cheapest","description":"Ordering only; every strategy honours the same filters and footprint cap."},"budget_credits":{"type":"integer","minimum":1,"description":"Ceiling for the whole basket, in credits."},"placement_type":{"type":"string","enum":["guest_post","niche_edit","link_insert","homepage_link","media_placement"]},"inventory_mode":{"$ref":"#/components/schemas/InventoryMode"},"dr_min":{"type":"number","minimum":0,"maximum":100,"description":"Minimum DR, 0-100. Matches the site's claimed DR, or the vendor's guaranteed minimum on blind inventory. Never the measured verified_dr, which is on a 0-1000 scale."},"rd_min":{"type":"number","minimum":0,"description":"Minimum guaranteed Majestic referring domains. Only blind tiers sold on RD carry this guarantee; the RD and DR scales are never translated into each other."},"traffic_min":{"type":"number","minimum":0,"description":"Minimum monthly traffic: measured where known, else claimed, else the blind guarantee floor."},"price_max":{"type":"integer","minimum":1,"description":"Maximum price per placement, USD minor units (cents)."},"price_min":{"type":"integer","minimum":1,"description":"Price floor, USD minor units. A quality filter, not a budget one: a price far below the going rate for the claimed DR is evidence the DR was bought."},"verified_only":{"type":"boolean","default":false,"description":"Only domains measured independently AND passed. Stricter than excluding suspect domains, because it also drops the unmeasured."},"include_restricted":{"type":"boolean","default":false,"description":"Gambling, adult and piracy sites are excluded unless topic/accepts names the vertical or this is true."},"plausibility_min_pct":{"type":"number","minimum":0,"maximum":100,"description":"Keep only placements at or above this percentile of their own price band (placement type x inventory mode x claimed-DR decade). The measured form of price_min."},"supplier":{"type":"string","maxLength":64,"description":"Opaque supplier token taken from the prefix of any offer_id. Filters to one source without naming it. Real supplier names are not accepted and match nothing."},"topic":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Harmonised subject (e.g. finance, travel, health, technology). Repeatable; repeats are OR-ed."},"accepts":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Restricted vertical the placement must accept (crypto, gambling, cbd, vape, adult, dating, pharma, forex). Repeatable; repeats are AND-ed. An empty accepts array on an offer means UNKNOWN, not 'accepts nothing'."},"dr_max":{"type":"number","minimum":0,"maximum":100},"geo":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"languages":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}]},"include_suspect":{"type":"boolean","default":false,"description":"Suspect domains are excluded unless asked for."},"prefer_demand":{"type":"boolean","default":false,"description":"Order eligible placements by observed demand (distinct buyers in twelve months) before price."},"prefer_leaders":{"type":"boolean","default":false,"description":"Order eligible placements by leader_links for the brief's topic before price. Give a topic so the right leaders count."},"exclude_domains_bought_for":{"type":"string","maxLength":2048,"description":"A target URL or domain. Domains this account already bought for it are left out (excluded.bought_for_target)."},"exclude_domains_linking_to":{"type":"string","maxLength":2048,"description":"A target URL or domain. Every domain in its backlink profile is left out (excluded.already_linking) and the response carries profile_hint. Fetches the profile if not cached (counts against the daily profile budget)."},"target_url":{"type":"string","format":"uri","maxLength":2048,"description":"The page this plan is for. Remembered per target (campaign memory); the weekly brief and standing orders re-run it."},"max_per_domain":{"type":"integer","minimum":1,"maximum":10,"default":1,"description":"Ten links from one site is one relationship."}}},"OrderContent":{"type":"object","description":"The article on an order: who writes it, what it cost, when it is due.","properties":{"mode":{"type":"string","enum":["publisher","service","supplied","papi_written"]},"tier":{"type":["string","null"]},"price_credits":{"type":"integer"},"due_at":{"type":["string","null"],"format":"date-time"},"if_no_content":{"type":["string","null"],"enum":["convert_to_service","cancel",null]},"version":{"type":"integer","description":"How many versions have been sent so far."},"awaiting":{"type":"boolean","description":"True in awaiting_content or content_revision_requested: the order is paid and held, not with a supplier."}}},"OutcomeKeyword":{"type":"object","properties":{"keyword":{"type":"string"},"baseline":{"type":["integer","null"]},"latest":{"type":["integer","null"]},"latest_checkpoint":{"type":["string","null"]},"delta":{"type":["integer","null"],"description":"baseline − latest: positive means it moved up."},"best":{"type":["integer","null"]},"series":{"type":"array","items":{"type":"object","properties":{"checkpoint":{"type":"string"},"rank":{"type":["integer","null"]},"observed_at":{"type":"string"}}}}}},"Outcome":{"type":["object","null"],"description":"Everything observed since a placement. Checkpoints: baseline_order, baseline_delivery, d7, d30, d60, d90, d180, d365. Observational: a rank that moved is a fact; that this link moved it is not established. Never say 'caused'.","properties":{"order_id":{"type":"string"},"target_url":{"type":"string"},"site_id":{"type":["string","null"]},"delivered_at":{"type":["string","null"]},"days_observed":{"type":["integer","null"]},"latest_checkpoint":{"type":["string","null"]},"keywords":{"type":"array","items":{"$ref":"#/components/schemas/OutcomeKeyword"}},"keywords_tracked":{"type":"integer"},"keywords_improved":{"type":"integer"},"keywords_declined":{"type":"integer"},"entered_top10":{"type":"integer"},"median_delta":{"type":["number","null"]},"volatility_proxy":{"type":["number","null"]},"serp_churn":{"type":["number","null"],"description":"Share of the baseline top-10 domains no longer in the latest top 10."},"visibility":{"type":"object","properties":{"baseline_traffic":{"type":["number","null"]},"latest_traffic":{"type":["number","null"]},"baseline_keywords":{"type":["integer","null"]},"latest_keywords":{"type":["integer","null"]}}},"survival":{"type":"object","properties":{"live":{"type":["boolean","null"]},"checks":{"type":"integer"},"last_checked":{"type":["string","null"]},"first_missing_at":{"type":["string","null"]}}},"confidence":{"type":"string","enum":["low","medium","high"],"description":"About the window, not the size of the movement: under 30 days is noise; one keyword is an anecdote; churn ≥50% says the SERP moved."},"confidence_note":{"type":"string"},"computed_at":{"type":"string","format":"date-time"}}},"Order":{"type":"object","description":"An order as listed by GET /v1/orders. Supplier identity is intentionally not exposed anywhere in this API. You are buying from us; which upstream marketplace fulfils a placement is not something you need or can act on.","properties":{"order_id":{"type":"string"},"status":{"$ref":"#/components/schemas/OrderStatus"},"offer_id":{"type":"string"},"placement_type":{"$ref":"#/components/schemas/PlacementType"},"inventory_mode":{"$ref":"#/components/schemas/InventoryMode"},"site_id":{"type":["string","null"]},"target_url":{"type":"string"},"anchor_text":{"type":["string","null"]},"anchor_plan":{"oneOf":[{"$ref":"#/components/schemas/AnchorPlan"},{"type":"null"}]},"target_keyword":{"type":["string","null"]},"quantity":{"type":"integer"},"total_credits":{"type":"integer","description":"Credits. 1 credit = 1 US cent."},"ordered_at":{"type":"string","format":"date-time"},"due_at":{"type":["string","null"],"format":"date-time"},"content":{"$ref":"#/components/schemas/OrderContent"},"cancellable":{"type":"boolean","description":"True while nothing has been sent to a supplier yet."},"submitted_at":{"type":["string","null"],"format":"date-time"},"delivered_at":{"type":["string","null"],"format":"date-time"},"placement_url":{"type":["string","null"]},"delivery_state":{"type":"string","enum":["in_progress","on_time","delivered_late","overdue","refund_eligible","unknown_turnaround","closed"],"description":"Derived at read time, never stored."},"days_late":{"type":["integer","null"]},"refund_eligible_at":{"type":["string","null"],"format":"date-time"},"supplier_report":{"type":["string","null"],"description":"The supplier's latest word from the check-in loop: completed | on_time | delayed, or null."},"supplier_report_at":{"type":["string","null"]},"link_audit":{"type":["object","null"],"description":"Our own audit of the delivered page: link present in served HTML, dofollow, bought anchor intact. Null before the first sweep.","properties":{"checked_at":{"type":"string"},"link_found":{"type":"boolean"},"dofollow":{"type":["boolean","null"]},"anchor_matched":{"type":["boolean","null"]}}},"turnaround_revised":{"type":"boolean"},"outcome":{"type":["object","null"],"description":"Compact outcome (no per-keyword series). `detail` names GET /v1/orders/{order_id}/outcome. Null until tracking has produced anything.","properties":{"days_observed":{"type":["integer","null"]},"latest_checkpoint":{"type":["string","null"]},"keywords":{"type":"array","items":{"type":"object"}},"keywords_improved":{"type":"integer"},"keywords_declined":{"type":"integer"},"entered_top10":{"type":"integer"},"median_delta":{"type":["number","null"]},"serp_churn":{"type":["number","null"]},"visibility":{"type":"object"},"survival":{"type":"object"},"confidence":{"type":"string","enum":["low","medium","high"]},"confidence_note":{"type":"string"},"detail":{"type":"string"}}}}},"StandingOrder":{"type":"object","description":"A plan that re-runs on a cadence. 'approve' mode emails a held quote with an approval link each run. 'auto' places only when the account owner has enabled auto placement from the dashboard (POST /v1/customers/me/auto, session only) and the caps and per-run budget clear; otherwise it emails an approval link and says why.","properties":{"standing_order_id":{"type":"string"},"target_url":{"type":"string"},"brief":{"type":"object","description":"The plan_links brief to re-run; links and budget are overridden by links_per_run and budget_credits_per_run."},"cadence":{"type":"string","enum":["weekly","biweekly","monthly"]},"links_per_run":{"type":"integer"},"budget_credits_per_run":{"type":"integer"},"mode":{"type":"string","enum":["approve","auto"]},"anchor_plan":{"oneOf":[{"$ref":"#/components/schemas/AnchorPlan"},{"type":"null"}]},"if_no_content":{"type":["string","null"],"enum":["convert_to_service","cancel",null]},"content_mode":{"type":["string","null"],"enum":["publisher","service","supplied","papi_written",null]},"status":{"type":"string","enum":["active","paused"]},"next_run_at":{"type":["string","null"]},"last_run_at":{"type":["string","null"]},"last_quote_id":{"type":["string","null"]},"last_outcome":{"type":["string","null"]},"last_outcome_detail":{"type":["string","null"]},"created_at":{"type":"string"}}},"SiteListing":{"type":"object","properties":{"offer_id":{"type":"string"},"supplier":{"type":"string","description":"A per-response ordinal (A, B, C…) so you can see that listings come from different suppliers, without identifying them."},"placement_type":{"$ref":"#/components/schemas/PlacementType"},"price":{"type":"object","properties":{"credits":{"type":["integer","null"]},"usd_minor":{"type":["integer","null"]},"currency":{"type":"string","const":"USD"}}},"comparable":{"type":"boolean","description":"False when the listing has no USD-normalised price."},"market_reference":{"$ref":"#/components/schemas/MarketReference"},"observed_at":{"type":"string"},"age_hours":{"type":"number"},"stale":{"type":"boolean"},"stale_after_hours":{"type":"integer"}}},"Site":{"type":"object","description":"The one place everything about a publisher is assembled. Use it for compare_domain and any 'tell me about this site' question rather than stitching offers together.","properties":{"configured":{"type":"boolean"},"found":{"type":"boolean","const":true},"site":{"type":"object","properties":{"site_id":{"type":"string"},"domain":{"type":"string"},"domain_hidden":{"type":"boolean"},"categories":{"type":"array","items":{"type":"string"}},"language":{"type":["string","null"]},"country":{"type":["string","null"]},"subdomain_of":{"type":["string","null"]}}},"verification":{"type":"object","properties":{"flag":{"$ref":"#/components/schemas/Verification"},"reasons":{"$ref":"#/components/schemas/QualityReasons"}}},"restricted":{"$ref":"#/components/schemas/Restricted"},"graph":{"type":"object","description":"Common Crawl web graph, root sites only (null fields on subdomains).","properties":{"outbound_external":{"type":["integer","null"]},"inbound_domains":{"type":["integer","null"]},"cc_pagerank":{"type":["number","null"]},"cc_harmonic":{"type":["number","null"]},"leader_links":{"$ref":"#/components/schemas/LeaderLinks"}}},"survival":{"$ref":"#/components/schemas/Survival"},"link_history":{"type":["object","null"],"description":"The decay curve behind survival: our placements checked at each checkpoint and how many were live then. Same three-order floor.","properties":{"orders":{"type":"integer"},"by_checkpoint":{"type":"array","items":{"type":"object","properties":{"checkpoint":{"type":"string"},"checked":{"type":"integer"},"live":{"type":"integer"}}}},"first_check_at":{"type":"string"},"last_check_at":{"type":"string"}}},"outcomes":{"type":["object","null"],"description":"Observed after placements on this publisher across every buyer; null under three distinct buyers. Observed, never caused."},"placement_score":{"$ref":"#/components/schemas/PlacementScore"},"demand":{"$ref":"#/components/schemas/Demand"},"supplier_count":{"type":"integer","description":"Distinct suppliers carrying the domain — why a price spread exists — without saying who."},"spread_usd_minor":{"type":["integer","null"]},"listings":{"type":"array","items":{"$ref":"#/components/schemas/SiteListing"},"description":"Cheapest first."},"metrics":{"type":"array","description":"Vendor claims and independent readings side by side, never merged.","items":{"type":"object","properties":{"source":{"type":"string"},"rank":{"type":["number","null"]},"traffic":{"type":["number","null"]},"referring_domains":{"type":["integer","null"]},"spam_score":{"type":["number","null"]},"as_of":{"type":["string","null"]},"fetched_at":{"type":"string"}}}}}}}},"paths":{"/v1":{"get":{"tags":["Meta"],"operationId":"service_banner","security":[],"summary":"Service banner","description":"Unauthenticated. Confirms the service is up and points at the authenticated endpoints and the MCP transport. Carries no inventory or pricing.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"service":{"type":"string"},"version":{"type":"string"},"docs":{"type":"string"},"schema":{"type":"string"},"mcp":{"type":"object"},"endpoints":{"type":"array","items":{"type":"string"}}}}}}}}}},"/v1/health":{"get":{"tags":["Meta"],"operationId":"health","summary":"Catalogue coverage and freshness","description":"Check this before trusting prices. Customers see coverage counts and whether every integrated supplier's snapshot is fresh (48h); the supplier roster itself is not returned.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"stale_after_hours":{"type":"integer"},"all_fresh":{"type":"boolean"},"coverage":{"type":"object","properties":{"suppliers_integrated":{"type":"integer"},"suppliers_stale":{"type":"integer"},"total_offers":{"type":"integer"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/schema":{"get":{"tags":["Meta"],"operationId":"field_semantics","summary":"Field semantics for agents","description":"Hand-authored, machine-readable description of what the fields MEAN (inventory modes, verification, pricing, anchor distribution, rate limits), for a model-driven client to self-configure against. Not a type schema; this document is.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"description":{"type":"string"},"critical_semantics":{"type":"object"},"mcp":{"type":"object"},"endpoints":{"type":"object"},"placement_types":{"type":"array","items":{"type":"string"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/offers":{"get":{"tags":["Catalogue"],"operationId":"search_placements","summary":"Search inventory","description":"Filtered catalogue rows, cheapest first, one per offer (a domain carried by several suppliers appears several times; POST /v1/quote collapses that). Unknown parameters are refused with 400 unknown_parameter. Rows count against the hourly and monthly read allowances; there is no geo filter — filter on the returned `geo` client-side.","parameters":[{"name":"placement_type","in":"query","required":false,"schema":{"type":"string","enum":["guest_post","niche_edit","link_insert","homepage_link","media_placement"]},"description":"Placement type."},{"name":"inventory_mode","in":"query","required":false,"schema":{"$ref":"#/components/schemas/InventoryMode"},"description":"named | pre_approval | blind."},{"name":"dr_min","in":"query","required":false,"schema":{"type":"number","minimum":0,"maximum":100},"description":"Minimum DR, 0-100. Matches the site's claimed DR, or the vendor's guaranteed minimum on blind inventory. Never the measured verified_dr, which is on a 0-1000 scale."},{"name":"rd_min","in":"query","required":false,"schema":{"type":"number","minimum":0},"description":"Minimum guaranteed Majestic referring domains. Only blind tiers sold on RD carry this guarantee; the RD and DR scales are never translated into each other."},{"name":"traffic_min","in":"query","required":false,"schema":{"type":"number","minimum":0},"description":"Minimum monthly traffic: measured where known, else claimed, else the blind guarantee floor."},{"name":"price_max","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Maximum price per placement, USD minor units (cents)."},{"name":"price_min","in":"query","required":false,"schema":{"type":"integer","minimum":1},"description":"Price floor, USD minor units. A quality filter, not a budget one: a price far below the going rate for the claimed DR is evidence the DR was bought."},{"name":"verified_only","in":"query","required":false,"schema":{"type":"string","enum":["true","false","1","0"]},"description":"Only domains measured independently AND passed. Stricter than excluding suspect domains, because it also drops the unmeasured."},{"name":"include_restricted","in":"query","required":false,"schema":{"type":"string","enum":["true","false","1","0"]},"description":"Gambling, adult and piracy sites are excluded unless topic/accepts names the vertical or this is true."},{"name":"plausibility_min_pct","in":"query","required":false,"schema":{"type":"number","minimum":0,"maximum":100},"description":"Keep only placements at or above this percentile of their own price band (placement type x inventory mode x claimed-DR decade). The measured form of price_min."},{"name":"supplier","in":"query","required":false,"schema":{"type":"string","maxLength":64},"description":"Opaque supplier token taken from the prefix of any offer_id. Filters to one source without naming it. Real supplier names are not accepted and match nothing."},{"name":"niche","in":"query","required":false,"schema":{"type":"string"},"description":"The supplier's own raw label. Prefer topic."},{"name":"topic","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true,"description":"Harmonised subject (e.g. finance, travel, health, technology). Repeatable; repeats are OR-ed."},{"name":"accepts","in":"query","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true,"description":"Restricted vertical the placement must accept (crypto, gambling, cbd, vape, adult, dating, pharma, forex). Repeatable; repeats are AND-ed. An empty accepts array on an offer means UNKNOWN, not 'accepts nothing'."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":500,"default":100},"description":"Rows per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"maximum":10000,"default":0},"description":"Maximum 10,000: the catalogue is for searching, not enumerating."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"count":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"offers":{"type":"array","items":{"$ref":"#/components/schemas/Offer"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/sites/{domain}":{"get":{"tags":["Catalogue"],"operationId":"compare_domain","summary":"Everything about one publisher","description":"All live listings for one domain, cheapest first, with verification, graph, survival, link history, outcomes (3-buyer floor), placement score, demand, market reference per listing, supplier_count and metric history. The domain is canonicalised, so www/scheme/trailing-slash variants resolve to the same site.","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string"},"description":"A domain or URL."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Site"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Domain not carried.","content":{"application/json":{"schema":{"type":"object","properties":{"configured":{"type":"boolean"},"site_id":{"type":"string"},"found":{"type":"boolean","const":false},"listings":{"type":"array","maxItems":0}}}}}}}}},"/v1/market":{"get":{"tags":["Catalogue"],"operationId":"market_prices","summary":"Price distribution for a slice","description":"What a slice of the market costs, as buyer prices in credits: one price per DOMAIN (the cheapest way to buy it here), so a domain carried by several suppliers counts once. Live, priced, non-delisted named inventory only.","parameters":[{"name":"placement_type","in":"query","required":false,"schema":{"type":"string","enum":["guest_post","niche_edit","link_insert","homepage_link","media_placement"]},"description":"Placement type."},{"name":"inventory_mode","in":"query","required":false,"schema":{"$ref":"#/components/schemas/InventoryMode"},"description":"Inventory mode."},{"name":"dr_min","in":"query","required":false,"schema":{"type":"number","minimum":0,"maximum":100},"description":"Claimed DR floor."},{"name":"dr_max","in":"query","required":false,"schema":{"type":"number","minimum":0,"maximum":100},"description":"Claimed DR ceiling."},{"name":"topic","in":"query","required":false,"schema":{"type":"string","maxLength":64},"description":"Harmonised site topic."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"filters":{"type":"object"},"count":{"type":"integer","description":"Distinct domains behind the distribution."},"offers_considered":{"type":"integer"},"truncated":{"type":"boolean"},"currency":{"type":"string","const":"USD"},"unit":{"type":"string"},"p25":{"type":"integer"},"median":{"type":"integer"},"p75":{"type":"integer"},"min":{"type":"integer"},"max":{"type":"integer"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"`no_inventory`: no live inventory matches that slice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/market/taste":{"get":{"tags":["Catalogue"],"operationId":"market_taste","summary":"What buyers in a niche actually buy","description":"Purchase profile of a topic from observed purchases: DR and traffic band shares, placement types, price quartiles, target categories. `insufficient: true` (with min_observations / min_buyers and no counts) under 30 observations or 3 buyers. `winners` and `probable_buyers` blocks from the web graph are present regardless. Shares are over at least three distinct buyers; no targets, no buyers. Cached one hour.","parameters":[{"name":"topic","in":"query","required":true,"schema":{"type":"string","maxLength":60},"description":"Harmonised topic, e.g. finance."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"taste":{"type":"object","properties":{"topic":{"type":"string"},"insufficient":{"type":"boolean"},"min_observations":{"type":"integer"},"min_buyers":{"type":"integer"},"observations":{"type":"integer"},"buyers":{"type":"integer"},"dr_bands":{"type":"array","items":{"type":"object"}},"traffic_bands":{"type":"array","items":{"type":"object"}},"placement_types":{"type":"array","items":{"type":"object"}},"price_credits":{"type":"object"},"top_categories":{"type":"array","items":{"type":"object"}},"winners":{"type":["object","null"]},"probable_buyers":{"type":["object","null"]}}},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/quote":{"post":{"tags":["Planning"],"operationId":"plan_links","summary":"A brief in, a held basket out","description":"Prefer this over assembling a plan from /v1/offers yourself: it collapses each domain to its cheapest seller across every supplier, spreads the basket across domains so it does not read as one relationship, and HOLDS the prices for 4 hours. Each line carries `why`, `relevance`, `subdomain_of`, `quality_reasons` and up to two `alternatives` held at the shown price. Redeem with POST /v1/orders once per line with quote_id set. Nothing is reserved or charged; quoting is free. Accepts a customer key or dashboard session; the operator token is refused.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Brief"}}}},"responses":{"201":{"description":"Held basket.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"`no_inventory`: nothing matches the brief. Carries `eligible` and `excluded` so you can see which filter to relax; do not retry the same brief.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`profile_budget_exhausted` when exclude_domains_linking_to needs a fresh profile beyond the daily budget.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/quotes/{quote_id}":{"get":{"tags":["Planning"],"operationId":"get_quote","security":[],"summary":"Read a quote by token","description":"The same body POST /v1/quote returned (lines, why, alternatives, expires_at) plus target_url and `expired`, with no customer data (funding, quote_token and view_url are stripped). Public and token-gated, for approval links. A wrong id or token is a 404. Ordering still needs the key or a card.","parameters":[{"name":"quote_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"quote_token from the quote."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Quote"},{"type":"object","properties":{"expired":{"type":"boolean"},"note":{"type":"string"}}}]}}}},"404":{"description":"`quote_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/campaigns":{"get":{"tags":["Planning"],"operationId":"campaign_status","summary":"What this account already bought for a target","description":"Campaign memory from the buyer's own orders, grouped as `exact` (the URL; null when only a domain was given) and `domain` (every order pointing anywhere on the host). Each view: orders, links, spend_credits, pending, delivered, failed, cancelled, links_last_30d, first/last_order_at, domains_used[], placement_types, anchor_mix, anchors[]. anchor_mix comes from anchor_plan where given, else from classifying anchor text. `outcomes` is an observed summary, not attribution.","parameters":[{"name":"target_url","in":"query","required":true,"schema":{"type":"string"},"description":"The page the links point at, or a bare domain."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target_url":{"type":["string","null"]},"target_domain":{"type":"string"},"outcomes":{},"exact":{"type":["object","null"]},"domain":{"type":"object"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/targets/profile":{"get":{"tags":["Research"],"operationId":"target_profile","summary":"Backlink profile of a target's domain","description":"From DataForSEO: referring_domains, backlinks, backlinks_rank, spam_score, rank_bands (each referring domain's own rank, 0-1000), dr_bands (claimed DR, carried domains only), topics, anchor_mix (pass brand and keyword so exact/partial are told from topical), velocity_12m, top_referring[], carried, carried_share. Two scales, never merged. Cached 7 days; 20 fresh fetches per customer per day, cached reads unlimited.","parameters":[{"name":"url","in":"query","required":true,"schema":{"type":"string"},"description":"A page URL or a domain."},{"name":"brand","in":"query","required":false,"schema":{"type":"string","maxLength":80},"description":"Brand name for anchor classification."},{"name":"keyword","in":"query","required":false,"schema":{"type":"string","maxLength":120},"description":"Target keyword for anchor classification."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target_url":{"type":"string"},"target_domain":{"type":"string"},"fetched_at":{"type":"string"},"cached":{"type":"boolean"},"referring_domains":{"type":["integer","null"]},"referring_main_domains":{"type":["integer","null"]},"backlinks":{"type":["integer","null"]},"backlinks_rank":{"type":["integer","null"]},"spam_score":{"type":["number","null"]},"first_seen":{"type":["string","null"]},"referring_fetched":{"type":"integer"},"rank_bands":{"type":"object"},"rank_bands_basis":{"type":"string"},"dr_bands":{"type":"object"},"dr_bands_basis":{"type":"string"},"topics":{"type":"array","items":{"type":"object"}},"anchor_mix":{"type":"object"},"anchors_classified":{"type":"integer"},"velocity_12m":{"type":"array","items":{"type":"object"}},"top_referring":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"rank":{"type":["integer","null"]},"link_rank":{"type":["integer","null"]},"first_seen":{"type":["string","null"]},"nofollow_share":{"type":["number","null"]},"carried":{"type":"boolean"}}}},"carried":{"type":"integer"},"carried_share":{"type":"number"},"cache":{"type":"object"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"description":"`profile_budget_exhausted`: 20 new backlink profiles per day.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/targets/gap":{"post":{"tags":["Research"],"operationId":"gap_map","summary":"Competitors' referring domains the target lacks, priced and held","description":"Domains linking to the competitors and not to the target, joined to the catalogue: rows we can sell come back as quote lines (held for 4 hours, orderable with quote_id exactly like /v1/quote lines) plus competitors_linking and linking_competitors; what we cannot sell is a count and a sample. Suspect domains are excluded. Without competitors, DataForSEO's backlink competitors are used and competitors_source says so. Cached 24h per target and competitor set; 20 fresh fetches per customer per day.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["target_url"],"additionalProperties":false,"properties":{"target_url":{"type":"string","maxLength":2048},"competitors":{"type":"array","maxItems":5,"items":{"type":"string"}},"topic":{"type":"string","maxLength":60},"placement_type":{"type":"string","maxLength":40},"limit":{"type":"integer","minimum":1,"maximum":100,"default":50}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"target_domain":{"type":"string"},"competitors":{"type":"array","items":{"type":"string"}},"competitors_source":{"type":"string"},"cached":{"type":"boolean"},"fetched_at":{"type":"string"},"gap_total":{"type":"integer"},"gap_fetched":{"type":"integer"},"carried":{"type":"integer"},"carried_share":{"type":"number"},"quote_id":{"type":["string","null"]},"expires_at":{"type":["string","null"]},"ttl_seconds":{"type":"integer"},"binding":{"type":"boolean"},"total_credits":{"type":"integer"},"excluded":{"type":"object"},"rows":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/QuoteLine"},{"type":"object","properties":{"competitors_linking":{"type":"integer"},"linking_competitors":{"type":"array","items":{"type":"string"}}}}]}},"not_carried":{"type":"integer"},"not_carried_sample":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"competitors_linking":{"type":"integer"},"link_rank":{"type":["integer","null"]}}}},"cache":{"type":"object"},"next":{"type":"object"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"`no_competitors`: none found sharing referring domains; name some.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`gap_budget_exhausted`: 20 new gap fetches per day.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/keywords/winners":{"get":{"tags":["Research"],"operationId":"keyword_winners","summary":"Who ranks for a keyword, who links to them, what we sell","description":"Top 30 ranking domains for a keyword (fresh SERP fetched within a 200/day budget when none under 60 days is held), the domains linking to two or more of them (or to one we carry), and which we carry with the cheapest offer_id, price_credits and verification. `leaders_with_referrers` says how many leaders have been profiled; shared_referrers is over those.","parameters":[{"name":"keyword","in":"query","required":true,"schema":{"type":"string","maxLength":120},"description":"The keyword."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keyword":{"type":"string"},"serp_fetched_at":{"type":["string","null"]},"serp_fresh":{"type":"boolean"},"leaders":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"position":{"type":"integer"},"url":{"type":"string"}}}},"leaders_with_referrers":{"type":"integer"},"shared_referrers":{"type":"array","items":{"type":"object","properties":{"domain":{"type":"string"},"leaders_linked":{"type":"integer"},"authority_rank":{"type":["integer","null"]},"carried":{"type":"boolean"},"offer_id":{"type":["string","null"]},"price_credits":{"type":["integer","null"]},"verification":{"$ref":"#/components/schemas/Verification"}}}},"carried":{"type":"integer"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"429":{"description":"`serp_budget_exhausted`: 200 fresh keyword SERPs per day.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/orders":{"post":{"tags":["Orders"],"operationId":"place_order","summary":"Place an order (pay-first)","description":"Debits credits immediately. Requires the `order` scope and an Idempotency-Key header. With quote_id the held price is charged instead of the live one and the freshness guard is bypassed; without it a stale catalogue price is refused. Content is decided now against what the listing allows: 'supplied' without inline `content` parks the order in awaiting_content for 14 days; 'papi_written' charges 1,500 credits with the order; inline content is validated first (422, nothing charged). Orders that carry an article are one placement each.","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["offer_id","target_url"],"properties":{"offer_id":{"type":"string","description":"From /v1/offers or a quote line / alternative."},"quote_id":{"type":"string","description":"Redeem a held price from POST /v1/quote or POST /v1/targets/gap."},"target_url":{"type":"string","format":"uri","description":"The page the link points at."},"anchor_text":{"type":["string","null"],"minLength":1,"maxLength":300,"description":"Omit when order_requirements marks it optional (publisher writes the anchor)."},"target_keyword":{"type":["string","null"],"maxLength":200,"description":"The query the page should rank for. Optional and worth sending: it seeds outcome tracking and anchor classification."},"target_market":{"type":["string","null"],"minLength":2,"maxLength":2,"description":"ISO country for the SERP this is judged in. Defaults to US."},"target_device":{"type":["string","null"],"enum":["desktop","mobile",null]},"anchor_plan":{"$ref":"#/components/schemas/AnchorPlan"},"quantity":{"type":"integer","minimum":1,"maximum":100,"default":1},"content_mode":{"type":"string","enum":["publisher","service","supplied","papi_written"],"description":"Defaults from order_requirements.content: 'publisher' where the publisher writes, else 'supplied'. Refused (400 content_not_accepted) where the listing does not allow it."},"content_tier":{"type":"string","maxLength":80,"description":"For 'service': a vendor_label (or tier) from order_requirements.content.options. Defaults to the cheapest paid tier."},"if_no_content":{"type":"string","enum":["convert_to_service","cancel"],"default":"convert_to_service","description":"What happens when supplied copy misses content.due_at."},"content":{"$ref":"#/components/schemas/ContentBody"}}}}}},"responses":{"200":{"description":"Idempotent replay of an earlier order with the same key.","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"replayed":{"type":"boolean","const":true}},"additionalProperties":true}}}},"201":{"description":"Placed.","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"replayed":{"type":"boolean","const":false},"status":{"type":"string","enum":["ordered","awaiting_content"]},"unit_price_credits":{"type":"integer"},"total_credits":{"type":"integer","description":"unit price x quantity + content price."},"content":{"type":"object","properties":{"mode":{"type":"string","enum":["publisher","service","supplied","papi_written"]},"tier":{"type":["string","null"]},"price_credits":{"type":"integer"},"due_at":{"type":["string","null"]},"if_no_content":{"type":["string","null"]},"fallback_price_credits":{"type":["integer","null"]},"submit":{"type":["string","null"],"description":"POST /v1/orders/{order_id}/content when the buyer owes the article."},"spec":{"$ref":"#/components/schemas/ContentSpec"},"validation":{"oneOf":[{"$ref":"#/components/schemas/ContentValidation"},{"type":"null"}]}}},"inventory_mode":{"$ref":"#/components/schemas/InventoryMode"},"site_id":{"type":["string","null"]},"note":{"type":"string","description":"Present on blind and pre_approval inventory: says plainly that no specific domain was sold."},"requires_approval":{"type":"boolean","description":"True on pre_approval inventory."}}}}}},"400":{"description":"`idempotency_key_required`, `invalid_json`, `bad_request`, `content_not_accepted`, `no_service_tier`, `one_article_per_order`, `unknown_content_tier`, `content_not_expected`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"`insufficient_credits`, with unit_price_credits and content_price_credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`insufficient_scope`, `customer_key_required`, or a ledger refusal: `per_order_cap_exceeded`, `daily_cap_exceeded`, `customer_inactive`, `customer_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`offer_not_found`, `quote_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`offer_delisted`, `offer_not_priceable`, `quote_expired` (with expires_at), `offer_not_in_quote`, `price_stale` (with observed_at).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`content_invalid`: the inline article would not pass the publisher; `validation` lists the problems. Nothing was charged.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}},"get":{"tags":["Orders"],"operationId":"list_orders","summary":"Your orders","description":"Newest first, with delivery state derived at read time, our latest link audit, and a compact observed outcome. `since` matches orders placed OR delivered at or after it (inclusive); a cancellation of an older order is not surfaced by it — re-read pending orders.","parameters":[{"name":"status","in":"query","required":false,"schema":{"$ref":"#/components/schemas/OrderStatus"},"description":"One order status."},{"name":"since","in":"query","required":false,"schema":{"type":"string","format":"date-time"},"description":"ISO-8601 timestamp."},{"name":"target_url","in":"query","required":false,"schema":{"type":"string","format":"uri"},"description":"Exact URL the links point at."},{"name":"target_domain","in":"query","required":false,"schema":{"type":"string"},"description":"Any order pointing anywhere on this domain."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Rows."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"count":{"type":"integer"},"filters":{"type":"object","description":"The filters as applied."},"orders":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/orders/{order_id}/cancel":{"post":{"tags":["Orders"],"operationId":"cancel_order","summary":"Cancel before it reaches a supplier","description":"Pulls an order back and refunds it in full, in the same statement. Orders are batched to suppliers each morning, so this is the window in which a mistake is cheaply reversible. Requires the `order` scope.","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"status":{"type":"string","const":"cancelled"},"credits_returned":{"type":"integer"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`: no cancellable order with that id (including another customer's).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`already_submitted`: the order is with the supplier; recovery is a refund request once the placement is confirmed missing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/orders/{order_id}/outcome":{"get":{"tags":["Orders"],"operationId":"order_outcome","summary":"Everything observed since one placement","description":"Per-keyword rank series across checkpoints, SERP churn, visibility before/after, survival checks, and a confidence with a note. Recomputed on read when the stored row is over an hour old. `outcome` is null with a note until tracking has started.","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"status":{"$ref":"#/components/schemas/OrderStatus"},"outcome":{"$ref":"#/components/schemas/Outcome"},"note":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`order_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/orders/{order_id}/content":{"post":{"tags":["Content"],"operationId":"submit_content","summary":"Send the article","description":"For an awaiting_content or content_revision_requested order. Validated against the publisher's spec; 422 lists the problems. `status: \"draft\"` stores a version without submitting (nothing charged, order status unchanged) — Papi's draft for approval; resend with `status: \"submitted\"` to approve. A submission moves the order to `ordered` for the next batch. Nothing further is charged; the papi_written fee was taken with the order. Requires the `order` scope.","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/ContentBody"},{"type":"object","properties":{"status":{"type":"string","enum":["draft","submitted"],"default":"submitted"},"submitted_by":{"type":"string","enum":["customer","papi"],"default":"customer"}}}]}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"version":{"type":"integer"},"content_status":{"type":"string","enum":["draft","submitted"]},"order_status":{"$ref":"#/components/schemas/OrderStatus"},"charged_credits":{"type":"integer"},"validation":{"$ref":"#/components/schemas/ContentValidation"},"next":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"A safety-net papi_written charge could not be covered; carries `validation`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`order_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`content_not_expected` (publisher or supplier writes), `order_not_open`, `content_already_submitted`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"`content_invalid`, with `validation`. Send status 'draft' to store it anyway.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}},"get":{"tags":["Content"],"operationId":"get_order_content","summary":"Every version sent","description":"All versions with their validation and any supplier rejection note (vendor_note), plus the spec and rules.","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"order_id":{"type":"string"},"order_status":{"$ref":"#/components/schemas/OrderStatus"},"content_mode":{"type":"string","enum":["publisher","service","supplied","papi_written"]},"spec":{"$ref":"#/components/schemas/ContentSpec"},"rules":{"type":"array","items":{"type":"string"}},"versions":{"type":"array","items":{"type":"object","properties":{"version":{"type":"integer"},"status":{"type":"string","description":"draft | submitted | rejected | superseded"},"submitted_by":{"type":"string"},"title":{"type":["string","null"]},"body_markdown":{"type":["string","null"]},"body_html":{"type":["string","null"]},"doc_url":{"type":["string","null"]},"meta_description":{"type":["string","null"]},"word_count":{"type":["integer","null"]},"validation":{"$ref":"#/components/schemas/ContentValidation"},"vendor_note":{"type":["string","null"]},"created_at":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`order_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/orders/{order_id}/content/papi":{"post":{"tags":["Content"],"operationId":"write_it_for_me","summary":"Hand the article to Placement Papi now","description":"For an awaiting_content or content_revision_requested order: charges the 1,500-credit tier once (nothing if a writing tier was already paid, or if already papi_written) and returns the order to awaiting_content at version 0 for Papi to draft. Requires the `order` scope.","parameters":[{"name":"order_id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"order_id":{"type":"string"},"status":{"type":"string"},"content_mode":{"type":"string"},"charged_credits":{"type":"integer"},"note":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"description":"Balance cannot cover the fee; carries price_credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`order_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`order_not_open`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/credits":{"get":{"tags":["Account"],"operationId":"check_balance","summary":"Balance, limits, allowance and recent ledger","description":"Balance is read from the append-only ledger. `funding.cash_on_hand_credits` is money that has cleared; a top-up is spendable immediately but an order is only SENT to a supplier once the money behind it settles. `read_allowance` shows the monthly row entitlement before a 429. `refund` shows what an unspent balance would return today.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"balance_credits":{"type":"integer","description":"Credits. 1 credit = 1 US cent."},"funding":{"type":["object","null"],"properties":{"cash_on_hand_credits":{"type":"integer"},"pending_credits":{"type":"integer"},"settles_immediately":{"type":"boolean"},"hold_hours":{"type":"integer"}}},"read_allowance":{"type":"object","properties":{"rows_used_this_month":{"type":"integer"},"rows_allowed_this_month":{"type":"integer"},"note":{"type":"string"}}},"balance_usd":{"type":"string","description":"Credits are pegged 1:1 to US cents, so this is exact."},"limits":{"type":"object","properties":{"per_order_max_credits":{"type":["integer","null"]},"daily_max_credits":{"type":["integer","null"]},"spent_today_credits":{"type":"integer"},"remaining_today_credits":{"type":["integer","null"]}}},"refund_grace_days":{"type":["integer","null"]},"volume_credits_earned":{"type":"integer"},"refund":{"type":"object","properties":{"refundable_credits":{"type":"integer"},"fee_credits":{"type":"integer"},"payout_credits":{"type":"integer"},"eligible":{"type":"boolean"},"reason":{"type":["string","null"]},"window_days":{"type":"integer"},"note":{"type":"string"}}},"recent_entries":{"type":"array","maxItems":50,"items":{"type":"object","properties":{"entry_id":{"type":"integer"},"kind":{"type":"string"},"amount_credits":{"type":"integer"},"order_id":{"type":["string","null"]},"memo":{"type":["string","null"]},"receipt_url":{"type":["string","null"]},"created_at":{"type":"string"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/credits/checkout":{"post":{"tags":["Account"],"operationId":"create_topup_checkout","summary":"Start a card top-up","description":"Returns a Stripe Checkout URL. Credits are granted by the payment webhook when the payment clears, not by this response. Card only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount_cents"],"properties":{"amount_cents":{"type":"integer","minimum":5000,"maximum":10000000,"description":"Cents, which is also credits ($50 to $100,000)."},"save_card":{"type":"boolean","default":false,"description":"Keep the card on file for auto-reload (recorded at Stripe as setup_future_usage)."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"checkout_url":{"type":["string","null"]},"session_id":{"type":"string"},"amount_cents":{"type":"integer"},"credits":{"type":"integer"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`customer_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/credits/settings":{"get":{"tags":["Account"],"operationId":"get_spend_settings","summary":"Spend caps and auto-reload","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"limits":{"type":"object","properties":{"per_order_max_credits":{"type":["integer","null"]},"daily_max_credits":{"type":["integer","null"]}}},"auto_reload":{"type":"object","properties":{"enabled":{"type":"boolean"},"threshold_credits":{"type":["integer","null"]},"amount_credits":{"type":["integer","null"]},"has_saved_card":{"type":"boolean"}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`customer_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}},"patch":{"tags":["Account"],"operationId":"update_spend_settings","summary":"Change spend caps (dashboard session only)","description":"NOT available to API keys: a key that could raise its own cap would not be a cap. Returns 403 `forbidden` for any Bearer key. Null clears a cap; an omitted field is left alone. Auto-reload needs both threshold and amount, or neither, and amount must exceed threshold.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"per_order_max_credits":{"type":["integer","null"],"minimum":1},"daily_max_credits":{"type":["integer","null"],"minimum":1},"auto_reload_threshold_credits":{"type":["integer","null"],"minimum":1},"auto_reload_amount_credits":{"type":["integer","null"],"minimum":5000,"maximum":10000000}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"limits":{"type":"object"},"auto_reload":{"type":"object","properties":{"threshold_credits":{"type":["integer","null"]},"amount_credits":{"type":["integer","null"]}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden`: API keys cannot change spend limits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/keys":{"get":{"tags":["Account"],"operationId":"list_keys","summary":"Your API keys","description":"Secrets are never returned; `prefix` is enough to recognise a key. `current` marks the key making the request.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"keys":{"type":"array","items":{"type":"object","properties":{"key_id":{"type":"string"},"name":{"type":["string","null"]},"prefix":{"type":"string"},"created_at":{"type":"string"},"last_used_at":{"type":["string","null"]},"revoked":{"type":"boolean"},"scopes":{"type":"array","items":{"type":"string","enum":["read","order"]}},"can_order":{"type":"boolean"},"current":{"type":"boolean"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}},"post":{"tags":["Account"],"operationId":"create_key","summary":"Issue a key","description":"The secret is shown once and never again. Scopes default to ['read']; a read-only key cannot issue a key with the `order` scope (403). At most 20 active keys.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":120},"scopes":{"type":"array","items":{"type":"string","enum":["read","order"]},"default":["read"]}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key_id":{"type":"string"},"name":{"type":["string","null"]},"scopes":{"type":"array","items":{"type":"string"}},"can_order":{"type":"boolean"},"key":{"type":"string","description":"The pk_live_… secret. Shown once."},"warning":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`forbidden`: a read-only key cannot mint an ordering key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`too_many_keys`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/keys/{key_id}":{"delete":{"tags":["Account"],"operationId":"revoke_key","summary":"Revoke a key","description":"Scoped to your own keys. Revoking the key you are using is refused with 409 unless `?confirm=self`.","parameters":[{"name":"key_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"confirm","in":"query","required":false,"schema":{"type":"string","enum":["self"]},"description":"Required to revoke the current key."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"key_id":{"type":"string"},"revoked":{"type":"boolean","const":true}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`would_revoke_current_key`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/customers/me/brief":{"get":{"tags":["Account"],"operationId":"get_weekly_brief","summary":"Weekly campaign brief settings","description":"Off by default. When on, once a week per target with recent orders (or the targets listed), the account email gets what landed, what is due, what moved, and a held quote for the next tranche with an approval link.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"weekday":{"type":"integer","minimum":1,"maximum":7,"description":"ISO weekday, 1 = Monday."},"targets":{"type":"array","items":{"type":"string"}},"last_sent_at":{"type":["string","null"]},"email":{"type":["string","null"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}},"patch":{"tags":["Account"],"operationId":"update_weekly_brief","summary":"Turn the weekly brief on or off","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"weekday":{"type":"integer","minimum":1,"maximum":7},"targets":{"type":"array","maxItems":50,"items":{"type":"string","maxLength":2048}}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean"},"weekday":{"type":"integer"},"targets":{"type":"array","items":{"type":"string"}},"last_sent_at":{"type":["string","null"]},"email":{"type":["string","null"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/customers/me/auto":{"get":{"tags":["Account"],"operationId":"get_auto_placement","summary":"Whether auto placement is enabled","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"auto_placement_enabled":{"type":"boolean"},"auto_ok_at":{"type":["string","null"]}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}},"post":{"tags":["Account"],"operationId":"set_auto_placement","summary":"Enable or disable auto placement (dashboard session only)","description":"NOT available to API keys: returns 403 `dashboard_only` for any Bearer key. This is the one switch that lets standing orders in 'auto' mode spend with nobody watching, so it takes a signed-in human on the account page. A key may create a standing order in auto mode; it cannot make that mode do anything.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"auto_placement_enabled":{"type":"boolean"},"auto_ok_at":{"type":["string","null"]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`dashboard_only`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/standing_orders":{"get":{"tags":["Account"],"operationId":"standing_orders","summary":"List standing orders","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"standing_orders":{"type":"array","items":{"$ref":"#/components/schemas/StandingOrder"}},"auto_placement_enabled":{"type":"boolean"},"note":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}},"post":{"tags":["Account"],"operationId":"create_standing_order","summary":"Create a standing order","description":"Requires the `order` scope. First run defaults to now + cadence. In auto mode with auto placement not enabled on the account, runs email an approval link instead and the response note says so. Auto-placed lines default to papi_written content where no content_mode is set, so an unattended run never stalls waiting for an article.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["target_url","cadence","links_per_run","budget_credits_per_run"],"properties":{"target_url":{"type":"string","format":"uri","maxLength":2048},"brief":{"type":"object","default":{},"description":"A plan_links brief; links and budget_credits are overridden per run."},"cadence":{"type":"string","enum":["weekly","biweekly","monthly"]},"links_per_run":{"type":"integer","minimum":1,"maximum":50},"budget_credits_per_run":{"type":"integer","minimum":100},"mode":{"type":"string","enum":["approve","auto"],"default":"approve"},"anchor_plan":{"$ref":"#/components/schemas/AnchorPlan"},"if_no_content":{"type":"string","enum":["convert_to_service","cancel"]},"content_mode":{"type":"string","enum":["publisher","service","supplied","papi_written"]},"first_run_at":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"standing_order_id":{"type":"string"},"next_run_at":{"type":"string"},"mode":{"type":"string"},"note":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/standing_orders/{id}":{"patch":{"tags":["Account"],"operationId":"update_standing_order","summary":"Pause, resume or edit a standing order","description":"Requires the `order` scope. Omitted fields are left alone; anchor_plan, if_no_content and content_mode accept null to clear.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"string","enum":["active","paused"]},"brief":{"type":"object"},"cadence":{"type":"string","enum":["weekly","biweekly","monthly"]},"links_per_run":{"type":"integer","minimum":1,"maximum":50},"budget_credits_per_run":{"type":"integer","minimum":100},"mode":{"type":"string","enum":["approve","auto"]},"anchor_plan":{"oneOf":[{"$ref":"#/components/schemas/AnchorPlan"},{"type":"null"}]},"if_no_content":{"type":["string","null"],"enum":["convert_to_service","cancel",null]},"content_mode":{"type":["string","null"],"enum":["publisher","service","supplied","papi_written",null]},"next_run_at":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StandingOrder"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}},"delete":{"tags":["Account"],"operationId":"delete_standing_order","summary":"Remove a standing order","description":"Requires the `order` scope.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"standing_order_id":{"type":"string"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/checkout/basket":{"post":{"tags":["Checkout"],"operationId":"checkout_basket","summary":"Buy a basket by card, with or without an account","description":"Auth optional. Without a key: `email` is required, payment creates the account, and the buyer receives a sign-in link with their orders placed. With a customer key: the balance is spent first and only the shortfall is charged; a basket the balance covers is placed immediately with no card (checkout_url null), and an Idempotency-Key header is REQUIRED. A card processing fee (2.9% + 30c, grossed up) is a separate line item. Nothing is charged or ordered by this call itself; orders are placed by the payment webhook once money is captured. A presented-but-invalid key is a 401, not a guest. Limited to 10 baskets an hour per address and per customer.","security":[{},{"apiKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Required when authenticated; optional for guests (the card form is the confirmation)."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["return_url","lines"],"properties":{"email":{"type":"string","format":"email","description":"Required when unauthenticated; ignored when a key is sent."},"return_url":{"type":"string","format":"uri","description":"https URL on an allowed host; where the buyer is sent back with ?basket=&token= appended."},"lines":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"object","required":["offer_id","target_url"],"properties":{"offer_id":{"type":"string"},"quote_id":{"type":"string"},"target_url":{"type":"string","format":"uri"},"anchor_text":{"type":["string","null"],"maxLength":300},"target_keyword":{"type":["string","null"],"maxLength":200},"content_mode":{"type":"string","enum":["publisher","service","supplied","papi_written"]},"content_tier":{"type":["string","null"],"maxLength":60,"description":"A paid writing tier by name when content_mode is service; the cheapest when omitted."},"if_no_content":{"type":"string","enum":["convert_to_service","cancel"]}}}}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"basket_id":{"type":"string"},"status_token":{"type":"string"},"checkout_url":{"type":["string","null"],"description":"Null when the balance covered everything."},"status":{"type":"string"},"total_credits":{"type":"integer","description":"placement_credits + content_credits."},"placement_credits":{"type":"integer"},"content_credits":{"type":"integer","description":"Writing fees priced into the basket (service tier or papi_written)."},"fee_cents":{"type":"integer"},"charge_cents":{"type":"integer"},"balance_used_credits":{"type":"integer"},"shortfall_credits":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"},"replayed":{"type":"boolean"},"lines":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"`bad_request`, `return_url_not_allowed`, `email_required`, `idempotency_key_required`, `empty_basket`, `anchor_text_required` (with offer_id).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"description":"`customer_key_required`: the operator token cannot buy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`repriced` with lines[{ offer_id, was_credits, now_credits }] when a held price moved more than 10%; `basket_already_resolved`; or a line problem such as `offer_not_found` naming the offer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/checkout/basket/{basket_id}":{"get":{"tags":["Checkout"],"operationId":"checkout_basket_status","security":[],"summary":"How a checkout went","description":"Public and token-gated, because the buyer may have no account yet. Poll after returning from the card form. A wrong token is a 404, not a 403. On `partial`, credits for unplaced lines stay on the buyer's balance. Says nothing else about the customer.","parameters":[{"name":"basket_id","in":"path","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","required":true,"schema":{"type":"string"},"description":"status_token from the checkout response."}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"pending | paid | placed | partial | failed | abandoned."},"total_credits":{"type":"integer"},"fee_cents":{"type":"integer"},"charge_cents":{"type":"integer"},"balance_used_credits":{"type":"integer"},"lines":{"type":"array","items":{"type":"object","properties":{"offer_id":{"type":"string"},"domain":{"type":["string","null"]},"price_credits":{"type":"integer"},"order_id":{"type":["string","null"]},"cancellable":{"type":"boolean"},"error":{"type":["string","null"]}}}},"dashboard_url":{"type":"string"},"email_hint":{"type":["string","null"],"description":"Masked."}}}}}},"404":{"description":"`not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/v1/observations/purchases":{"post":{"tags":["Research"],"operationId":"report_purchases","security":[],"summary":"Report placements bought anywhere","description":"Open to all; rate limited per address (5,000 rows/hour). Send your API key to be counted as a distinct buyer; anonymous reports are stored but count as one buyer per sending address. Feeds demand and niche taste, read only in aggregate behind a three-buyer floor. Anchor text is never stored, only its type; a buyer_ref that looks like an email is hashed; duplicates within 30 days are one observation. Nothing sent here is returned to another caller.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["buyer_ref","target_domain","rows"],"properties":{"buyer_ref":{"type":"string","maxLength":320},"target_domain":{"type":"string"},"target_url":{"type":["string","null"],"format":"uri"},"target_topics":{"type":["array","null"],"maxItems":12,"items":{"type":"string","maxLength":60}},"target_category":{"type":["string","null"],"maxLength":200},"rows":{"type":"array","minItems":1,"maxItems":200,"items":{"type":"object","required":["placement_domain"],"properties":{"placement_domain":{"type":"string"},"placement_type":{"type":["string","null"],"enum":["guest_post","niche_edit","link_insert","homepage_link","media_placement",null]},"price_paid_credits":{"type":["integer","null"]},"vendor_label":{"type":["string","null"],"maxLength":120},"anchor_type":{"type":["string","null"],"maxLength":40},"purchased_at":{"type":["string","null"]},"raw":{}}}}}}}}},"responses":{"201":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"accepted":{"type":"integer"},"enriched":{"type":"integer"},"not_carried":{"type":"integer"},"duplicates":{"type":"integer"},"rejected":{"type":"array","items":{}}}}}}},"400":{"description":"`bad_request`, or nothing accepted (same counts body).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"$ref":"#/components/responses/Unavailable"}}}}}}