---
title: "A brief in, a held basket out"
method: POST
path: "/v1/quote"
tags: ["Planning"]
---

# A brief in, a held basket out

`POST /v1/quote`

## Request

- Base URL: `https://api.placementapi.com`
- URL: `https://api.placementapi.com/v1/quote`
- Auth: HTTP bearer

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.

## Request body

- Brief: 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.
  - `links` integer, required: How many placements to plan.
  - `strategy` 'cheapest' | 'fastest' | 'safest' | 'recommended': Every strategy honours the same filters, quality gates, footprint cap and budget; cheapest is the default. fastest orders by turnaround, and safest prefers independently measured domains with real search traffic — those three change ordering only. recommended is additive and quality-first: measured-and-passed publishers before unmeasured ones, then the existing placement_score descending, with price (and offer id) only as deterministic ties and budget feasibility. In a deployment whose semantic layer runs in `rank` mode, recommended also reorders placements by a rank-usable judgment within the explicit demand/leader priorities; it omits nothing, and missing, failed or below-threshold evidence falls back to this same deterministic result and the same price. Its retrieval cut is a bounded quality proxy, not an exact global score ranking.
  - `budget_credits` integer: Ceiling for the whole basket, in credits.
  - `placement_type` 'guest_post' | 'niche_edit' | 'link_insert' | 'homepage_link' | 'media_placement'
  - `inventory_mode` 'named' | 'pre_approval' | 'blind': 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.
  - `dr_min` number: 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` number: 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` number: Minimum monthly traffic: measured where known, else claimed, else the blind guarantee floor.
  - `price_max` integer: Maximum price per placement, USD minor units (cents).
  - `price_min` integer: 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` boolean: Only domains measured independently AND passed. Stricter than excluding suspect domains, because it also drops the unmeasured.
  - `include_restricted` boolean: Gambling, adult and piracy sites are excluded unless topic/accepts names the vertical or this is true.
  - `plausibility_min_pct` number: 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` string: 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` union: Harmonised subject (e.g. finance, travel, health, technology). Repeatable; repeats are OR-ed.
    - string
    - string[]
  - `accepts` union: 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'.
    - string
    - string[]
  - `dr_max` number
  - `geo` union
    - string
    - string[]
  - `languages` union
    - string
    - string[]
  - `include_suspect` boolean: Suspect domains are excluded unless asked for.
  - `prefer_demand` boolean: Order eligible placements by observed demand (distinct buyers in twelve months) before price.
  - `prefer_leaders` boolean: 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` string: A target URL or domain. Domains this account already bought for it are left out (excluded.bought_for_target).
  - `exclude_domains_linking_to` string: 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` string, uri: The page this plan is for. Remembered per target (campaign memory); the weekly brief and standing orders re-run it.
  - `max_per_domain` integer: Ten links from one site is one relationship.

## Response `201`

Held basket.

- Quote: 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).
  - `quote_id` string
  - `quote_token` string: 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` string, uri
  - `target_url` string, nullable
  - `expires_at` string, date-time
  - `ttl_seconds` 14400
  - `binding` true: These prices are ours to honour until they lapse, even if the catalogue moves.
  - `strategy` 'cheapest' | 'fastest' | 'safest' | 'recommended': The strategy the basket was assembled with, echoed. Every strategy honours the same filters, quality gates, footprint cap and budget. `cheapest`, `fastest` and `safest` change ordering only; `recommended` in a deployment whose semantic layer runs in `rank` mode can also reorder placements by a rank-usable judgment, within the explicit demand/leader priorities. No strategy and no judgment removes a placement or an alternative.
  - `requested` integer
  - `filled` integer
  - `eligible` integer: Distinct options this brief can buy, before the cap: collapsed domain+placement slots the sourcing read could quote after the filters and, when budgeted, individual affordability. A semantic annotation never removes a slot, so this is the count the deterministic engine would have produced; `candidate_coverage` holds the pre-semantic counts.
  - `candidate_coverage` object: How much of the eligible catalogue the candidate query looked at, from the same snapshot as the lines. Discovery and sourcing counts, taken before any semantic policy runs: a semantic annotation changes neither `eligible` nor these counts, because it removes nothing. `eligible` above counts collapsed domain+placement slots; this counts candidate OFFERS before the collapse, so it is the field that shows whether the 4,000-offer retrieval cap bound the result. eligible_offers equal to retrieved_offers means every matching offer was ranked; a larger eligible_offers with truncated true means the basket was chosen from the first 4,000 in the brief's requested priority (demand, leaders, then fastest/safest/cheapest/recommended, with raw price and offer id as the deterministic tiebreak). Raw-price retrieval is an approximation of the final buyer-credit sort, and budget fill can end a basket before the cap matters, so truncated false is not a promise that the basket is globally optimal. `sourced_offers` and the fields beside it describe the second, bound read: the sourcing expansion re-reads every eligible seller of each chosen placement, so the winner per placement is chosen from the whole supplier set rather than from the retrieval cut, and a placement is either sourced whole or not at all. Aggregate counts only.
    - `eligible_offers` integer: Offers matching eligibility and, when budgeted, individual affordability, before the 4,000-offer cap and before any semantic policy. Exclusion counts retain their original filter-only basis.
    - `retrieved_offers` integer: Offers the candidate query returned. Equals eligible_offers unless truncated.
    - `limit` integer: The retrieval cap, in offers. 4000.
    - `truncated` boolean: eligible_offers exceeded the cap, so selection saw a subset of what matched.
    - `sourced_offers` integer: Offers the sourcing expansion returned for the chosen placements. Equal to retrieved_offers when the retrieved rows already carried every seller of every placement; larger when the cut had kept a dearer seller and left a cheaper one outside it.
    - `sourcing_limit` integer: The sourcing cap, in offers. 40000.
    - `sourcing_truncated` boolean: The expansion did not answer for every chosen placement, so fewer placements were quotable than retrieval found. Never means a placement was priced from a partial supplier set.
    - `dropped_slots` integer: Chosen placements the expansion did not return, and so did not quote. Placements, not offers; zero unless sourcing_truncated.
  - `decision` object, nullable: Aggregate semantic-annotation coverage for this basket. Present only when the request carried customer context, at least one placement was affordable, and a semantic service was reachable — absent otherwise, which is not the same as 'judged and found nothing'. `mode` is the service's mode (off | shadow | rank) or `unavailable` when it was configured for the deployment but could not be built or failed; `status` is the service's own coverage status, or `unavailable` for that same failure. `rank` is the only mode that can change a `recommended` basket, and only in that strategy: a rank-usable annotation may reorder it within the explicit demand/leader priorities, and no judgment — below threshold, unjudged, missing, invalid or failed — removes anything. `shadow` annotates only, and `cheapest`, `fastest` and `safest` are unchanged. The service judges a sampled publisher page resolved by site id, not the offered placement, so no judgment is a verdict on whether to buy a slot. Topical fit is a semantic judgment on the service's own scale, NOT a probability of SEO impact, and it never affects price. Aggregate counts only — no customer ids, provider state or evidence text.
    - `mode` 'off' | 'shadow' | 'rank' | 'unavailable'
    - `status` 'ok' | 'degraded' | 'insufficient_evidence' | 'invalid_config' | 'unavailable'
    - `coverage` object: How much of the semantic layer's candidate input was judged for this basket. `total_candidates` is the raw input length, `eligible_candidates` every valid eligible unique candidate id before the page service's own cap, and `selected_candidates` the BOUNDED page-semantic shortlist the service actually selected after its site-diverse fan-out and `maxCandidates` cap — the three are separately meaningful and only ever get smaller in that order, so on a wide brief `eligible_candidates` is larger than the bounded shortlist. Every selected candidate ends in exactly one of `evaluated`, `missing_evidence`, `invalid_evidence` or `runtime_failed`, so those four always sum to `selected_candidates`; a missing, failed or refused reading is never counted as a judgment, and an unselected eligible candidate is counted by none of them. Aggregate counts only.
      - `total_candidates` integer: The raw candidate input length, before validation, deduplication or any cap.
      - `eligible_candidates` integer: Every valid eligible unique candidate id, before the page service's own cap. This is the FULL eligible input set, not the bounded semantic shortlist, and it can exceed `selected_candidates`.
      - `selected_candidates` integer: The bounded page-semantic shortlist: the valid eligible unique candidates the service actually selected after its site-diverse fan-out and `maxCandidates` cap. Additive, and the only bounded shortlist denominator: `evaluated + missing_evidence + invalid_evidence + runtime_failed` always equals it.
      - `evaluated` integer
      - `policy_usable` integer
      - `missing_evidence` integer
      - `invalid_evidence` integer
      - `runtime_failed` integer
    - `identity_coverage` object: Additive, aggregate-only publisher-identity routing coverage. It answers a second question beside `coverage`, which keeps its own counts and status untouched: when this basket's page layer had no sampled page evidence for a candidate, did the durable publisher identity still settle a requested topic? `status` is `supported` when the routing preference was in force (a `recommended` brief whose confirmed mode was `rank`) and its one bounded identity read answered — which includes a successful empty read, reported as `supported` with zero counts, never as a failure; `unavailable` when that read did not answer or the page service failed so no shortlist exists; and `not_applicable` when no routing preference applied at all (no configured preference, another strategy, no recognized requested topic, or `off`/`shadow`, which annotate only). Counts are `missing_evidence` candidates only: a candidate counts here only when its own page annotation status was exactly `no_evidence` AND its site held a fresh supported primary-identity match for a requested topic, so refused or failed readings (`invalid_evidence`, `unavailable`, `timeout`, runtime failures) are never covered and `coverage.missing_evidence` is never reduced. Every count is over the page service's own bounded SELECTED shortlist — the bounded page-semantic set the service chose out of the full eligible input, so `shortlist_candidates` equals `coverage.selected_candidates` and never `coverage.eligible_candidates` — and `shortlist_candidates` is the annotated part of it — the candidates that came back with an explicit annotation, `no_evidence` included — so it is not a count of candidates the semantics evaluated. Aggregate counts and a fixed status only: no site ids, topics, facets, evidence ids, excerpts, provider payload or confidence values.
      - `status` 'supported' | 'unavailable' | 'not_applicable'
      - `shortlist_candidates` integer: The page service's own bounded shortlist of SELECTED candidates — the candidates it selected and returned an explicit annotation for, including `no_evidence` (the page policy's "not measured" reading, not a judgment). For a service that annotates exactly what it selected this is the same set `coverage.selected_candidates` counts, and never `coverage.eligible_candidates`, which is the larger full eligible input before the service's own cap. Not a count of candidates the semantics evaluated; every other count here is a subset of this one.
      - `missing_page_evidence` integer: That subset whose page annotation status was exactly `no_evidence`. Same number as `coverage.missing_evidence` when the aggregate is `supported`.
      - `supported_identity_missing_page_evidence` integer: The subset of `missing_page_evidence` whose site also held a fresh supported primary-identity match for one of the brief's requested topics. A routing coverage gate passes a degraded page decision only when this equals `missing_evidence`.
    - `publisher_context_coverage` object: Additive, aggregate-only projected publisher-context coverage. It answers a second, separate question beside `coverage` and `identity_coverage`, both of which keep their own counts and statuses untouched: when this basket's page layer had no sampled page evidence for a candidate, did the publisher context the request's OWN route projection supplied cover it? `status` is `supported` only for the frame in which those projected facts were in force — a `recommended` brief whose confirmed mode was `rank`, whose route attempt answered from a structurally valid `state_usable` payload, with the identity composition present and a recognized requested topic; `unavailable` when that frame was in force and the page service answered but the route projection did not supply the context facts (the attempt was not state-usable, so the exact predicates ran); and `not_applicable` when no such frame applied (another strategy, `off`/`shadow`, no identity composition, or no recognized requested topic). Counts describe one annotated shortlist — the page service's own bounded selected shortlist, the set `coverage.selected_candidates` counts, never the larger full eligible input `coverage.eligible_candidates` — and may overlap; they are never summed into a score. `missing_page_evidence` is the page policy's own not-measured reading, and `context_supported_missing_page_evidence` is the SUBSET of `missing_page_evidence` whose candidate site is `context_classified` OR qualifies as exceptional independent authority — the covered part of the page gap, never the page gap itself. A positive primary or prominent match is NOT required for coverage: a classified non-match remains classified, and a publisher whose independent authority clears this catalogue's measured cut-offs covers a missing page reading without any category designation. A refused or failed reading (`invalid_evidence`, `runtime_failed`, `unavailable`, `timeout`) is never covered, a malformed or refusable route attempt is never covered, and `coverage.missing_evidence` is never reduced. Routing completeness requires this covered-subset count to equal `missing_page_evidence`, so a page gap that is only partly covered is not routing-complete. Aggregate counts and a fixed status only: no site ids, topics, facets, evidence ids, excerpts, URLs, provider payload or confidence values.
      - `status` 'supported' | 'unavailable' | 'not_applicable'
      - `shortlist_candidates` integer: The page service's own bounded shortlist of SELECTED candidates — the candidates it selected and returned an explicit annotation for, including `no_evidence` (the page policy's "not measured" reading, not a judgment). The routing gate requires it to equal `coverage.selected_candidates`, and it is never `coverage.eligible_candidates`, which is the larger full eligible input before the service's own cap. Not a count of candidates the semantics evaluated; every other count here is a subset of this one.
      - `context_classified` integer: That subset whose site the projection reports as classified at all. Coverage only: it is not a positive topic match, a preference, a score or an eligibility term.
      - `primary_topic_matches` integer: That subset whose site held a supported requested primary-identity match. Reported beside the other bases and never required for coverage; counts may overlap.
      - `prominent_profile_matches` integer: That subset whose site held a requested PROMINENT sampled profile topic — a sampled publisher URL distribution over publisher-supplied sitemap metadata, not a primary identity. Reported, never required for coverage; counts may overlap.
      - `exceptional_authority` integer: That subset whose root domain the existing independent-authority reading qualifies (measured quality `ok`, fresh independent traffic and Common Crawl PageRank at or above this catalogue's measured cut-offs, nothing from a seller's own claims). Reach is not editorial relevance and claims no SEO impact.
      - `missing_page_evidence` integer: That subset whose page annotation status was exactly `no_evidence`. Same number as `coverage.missing_evidence` when the aggregate is `supported`.
      - `context_supported_missing_page_evidence` integer: The SUBSET of `missing_page_evidence` whose candidate site is `context_classified` OR qualifies as exceptional independent authority — the covered part of the page gap, never the page gap itself. Classification is coverage, not a requested-topic match, so a positive primary or prominent match is not required for a candidate to be counted here, and a challenged reading (`invalid_evidence`, `runtime_failed`, `unavailable`, `timeout`) is never covered. Routing completeness requires this covered-subset count to equal `missing_page_evidence`: a page gap covered only in part is not routing-complete.
  - `excluded` object: 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. Every key is a SQL gate: a semantic annotation adds none, because it removes no placement, and the evidence behind a below-threshold judgment is a sampled publisher page rather than the placement actually on offer.
  - `total_credits` integer: Credits. 1 credit = 1 US cent.
  - `at_median_supplier_credits` integer: The same basket priced at the median supplier for each domain.
  - `saving_vs_median_credits` integer
  - `funding` object, nullable
    - `balance_credits` integer
    - `sufficient` boolean
    - `shortfall_credits` integer
  - `lines` QuoteLine[]
    - `offer_id` string
    - `site_id` string, nullable: Null on blind inventory.
    - `inventory_mode` 'named' | 'pre_approval' | 'blind': 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.
    - `placement_type` 'guest_post' | 'niche_edit' | 'link_insert' | 'homepage_link' | 'media_placement'
    - `price_credits` integer: Held price. Credits. 1 credit = 1 US cent.
    - `dr` number, nullable: The vendor's claim. On a subdomain, the parent's number.
    - `traffic` number, nullable
    - `verification` 'ok' | 'suspect' | 'unverified' | 'null', nullable: 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.
    - `verified_dr` number, nullable: DataForSEO backlinks rank, 0-1000; corroborates, does not replace dr.
    - `verified_traffic` number, nullable
    - `organic_keywords` integer, nullable
    - `referring_domains` integer, nullable
    - `spam_score` number, nullable
    - `subdomain_of` string, nullable
    - `quality_reasons` string[]: 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` string[]: 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).
    - `outbound_external` integer, nullable
    - `inbound_domains` integer, nullable
    - `cc_pagerank` number, nullable
    - `leader_links` LeaderLinks, nullable: 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.
    - `survival` Survival, nullable: 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.
      - `orders` integer
      - `checks` integer
      - `live_share` number
      - `live_90d_share` number, nullable
      - `median_days_to_missing` number, nullable
      - `dofollow_share` number, nullable
      - `noindex_share` number, nullable
      - `updated_at` string, date-time
    - `supplier_reliability` SupplierReliability: 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.
      - `band` 'high' | 'medium' | 'low' | 'unknown'
      - `n` integer
    - `placement_score` PlacementScore, nullable: 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.
      - `score` integer
      - `version` string
      - `confidence` 'high' | 'medium' | 'low': Share of the weight that had a measurement behind it.
      - `components` object
        - `authority` number, nullable
        - `visibility` number, nullable
        - `quality` number, nullable
        - `graph` number, nullable
        - `survival` number, nullable
      - `capped` 'suspect' | 'null', nullable
    - `market_reference` MarketReference, nullable: 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.
      - `reference_credits` integer
      - `method` 'band_median_v1'
      - `comparables_n` integer
      - `vs_reference_pct` number: This price against the reference, in percent; negative is cheaper.
      - `assessment` 'under' | 'at' | 'over': ±15% band around the reference.
      - `own_asks` object
        - `listings` integer
        - `suppliers` integer
        - `lowest` integer
        - `median` integer
        - `highest` integer
      - `ask_history` object, nullable
        - `first_seen_at` string, date-time
        - `days_observed` integer
        - `first_credits` integer
        - `change_pct` number
    - `plausibility` Plausibility, nullable: 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.
      - `pct` number
      - `band` string
      - `flag` 'low' | 'ok' | 'high'
      - `n` integer: Domains behind the distribution.
    - `demand` Demand, nullable: 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.
      - `buyers_12m` integer
      - `purchases_12m` integer
      - `niches` object[]
        - `topic` string
        - `buyers` integer
    - `why` string[]: Factual sentences behind the pick, derived from the fields on this line and the brief — cite them. Prose; do not parse it.
    - `intelligence` object: Present only when the deployment has placement intelligence configured and this placement was actually judged; absent means not judged, which is not the same as judged and rejected. An advisory semantic judgment. In a `recommended` basket running in configured `rank` mode a rank-usable judgment can reorder a line within the explicit demand/leader priorities; it can never exclude a placement, an alternative or a publisher, and missing, invalid, failed or below-threshold evidence leaves the deterministic result — and every price — unchanged. The service judges a sampled publisher page resolved by site id, not the offered placement itself, so a below-threshold reading is not evidence against buying this slot. `topical_fit` and `confidence` are on the service's own scale — not a probability of SEO impact. No provider payload, evidence text or customer id is ever included.
      - `status` string: computed | cached when a judgment exists; disabled | insufficient_evidence | no_evidence | invalid_evidence | unavailable | budget_exhausted | busy | invalid_response | timeout otherwise.
      - `rank_usable` boolean: True only in rank mode when the service's own policy called the judgment usable. In a `recommended` basket a rank-usable judgment is what can reorder the placement; no judgment, usable or not, removes one.
      - `topical_fit` number, nullable: The judged fit to the brief's recognized topics, on the service's own scale; null when nothing usable was produced.
      - `confidence` number, nullable
      - `editorial_format` string, nullable
      - `policy_version` string, nullable
    - `relevance` unknown
    - `alternatives` QuoteAlternative[]
      - `offer_id` string
      - `site_id` string, nullable
      - `placement_type` 'guest_post' | 'niche_edit' | 'link_insert' | 'homepage_link' | 'media_placement'
      - `price_credits` integer
      - `dr` number, nullable
      - `verification` 'ok' | 'suspect' | 'unverified' | 'null', nullable: 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.
      - `plausibility_pct` number, nullable
    - `turnaround_days` integer, nullable
    - `topics` string[]
    - `guarantees` unknown
    - `order_requirements` OrderRequirements, nullable: 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.
      - `required` string[]: Always includes target_url; anchor_text is absent only where the publisher writes the anchor.
      - `optional` string[]
      - `content` object
        - `supplied_allowed` boolean: Whether you may write the article yourself.
        - `spec` ContentSpec, nullable: The brief to write against when the buyer supplies copy. Null when copy cannot be supplied.
          - `word_count_min` integer, nullable
          - `word_count_max` integer, nullable
          - `links_allowed` integer
          - `anchor_types_forbidden` string[]
          - `rules` string[]: Editorial rules in the publisher's own words.
        - `options` ContentOption[]
          - `tier` 'supplied' | 'included' | 'standard' | 'native' | 'papi_written'
          - `vendor_label` string, nullable: Pass as content_tier for content_mode 'service'.
          - `price_credits` integer: Add-on cost. Credits. 1 credit = 1 US cent.
          - `word_count` integer, nullable
          - `note` string, nullable
        - `due_days` integer, nullable: Days a supplied article may take to arrive after ordering (14); null where the publisher writes.
      - `speed` object
        - `standard_days` integer, nullable
        - `expedited_days` integer, nullable
        - `expedited_surcharge_bps` integer, nullable
      - `confirmation_required` boolean: True when a human at the supplier confirms the line before it is placed; treat the order as pending rather than done.
      - `rules` string[]: Placement rules the buyer must respect regardless of who writes the copy.
      - `confidence` 'observed' | 'documented' | 'assumed'
  - `profile_hint` object: Only when exclude_domains_linking_to was given: the target's profile bands beside the basket's.
    - `target_domain` string
    - `referring_domains_fetched` integer
    - `profile_rank_bands_share` object
    - `profile_rank_bands_basis` string
    - `carried_referring_domains` integer
    - `profile_dr_bands_share` object
    - `profile_dr_bands_basis` string
    - `profile_heaviest_dr_band` unknown
    - `basket_dr_bands` object
    - `dr_band_suggested` unknown
  - `next` object
    - `order` string
    - `note` string

## Other responses

- `400`: Malformed request. `error` is `bad_request` (with a zod `detail`), `invalid_json`, or `unknown_parameter` / `unknown_field` (with `unknown[]` and `accepted[]`).
- `401`: Missing, malformed or revoked Bearer credential: `{ error: "unauthorized" }`.
- `403`: `insufficient_scope` when the key lacks the `order` scope; `customer_key_required` when the operator token is used where a customer must be attributable.
- `409`: `no_inventory`: nothing matches the brief, or the brief's own gates and budget removed everything. Carries `eligible`, `candidate_coverage` and `excluded` so you can see what removed it; a semantic annotation never does (it can only reorder a `recommended` basket). Widen a filter or change the brief; do not retry it unchanged.
- `429`: `profile_budget_exhausted` when exclude_domains_linking_to needs a fresh profile beyond the daily budget.
- `503`: `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.

---

[API](https://skmtc.dev/placementapi/apis/placement-api.md) · [All operations](https://skmtc.dev/placementapi/apis/placement-api/llms.txt) · [OpenAPI document](https://skmtc.dev/placementapi/apis/placement-api/revisions/fc4e7866eaf7?raw)
