# UpDash Mail API > Order automotive direct mail (postcards and letters) for a car dealer from Xcel Media Group through one backend: a REST API under /v1 and seventeen MCP tools at /api/mcp (Streamable HTTP) that run the same code. A partner's staff also use the partner console at /console. Live at https://api.updash.com. Sandbox access is self-serve at https://www.updash.com/developers/agents: the form emails a one-time sign-in link to the partner console, where an admin creates the sandbox key (Developers, then API keys). Live ordering starts after Xcel onboards the partner. Phase 1 is a pilot: live orders are produced by Xcel staff and prices are placeholders. Proofs print to PDF where the server has Chromium, and are HTML previews otherwise; either way the API never returns them. Schema: [/openapi.json](/openapi.json) (OpenAPI 3.1). Every page below in one file: [/llms-full.txt](/llms-full.txt). Flow (MCP tool, then REST): `list_mail_products` (`GET /v1/products`) -> `get_dealer_brand` (`GET /v1/dealers/{id}/brand`) -> `create_campaign` (`POST /v1/campaigns`) -> `upload_audience` for a signed URL (`POST /v1/campaigns/{id}/audience`) -> PUT the CSV to that URL -> `upload_audience` again with `uploaded_audience_id` (`POST /v1/campaigns/{id}/audience/complete`) -> `generate_creative` -> `render_proof` -> `validate_campaign` -> `get_quote` -> `request_approval` -> a human decides on the review page -> `submit_campaign` -> `get_campaign_status` / `get_campaign_results`. Rules an agent must follow: - Auth: `Authorization: Bearer xm_test_...` (sandbox) or `Bearer xm_live_...` (live), on REST and MCP alike. MCP clients can instead connect by OAuth 2.1 from the URL alone: the 401 points at `/.well-known/oauth-protected-resource`, the person pastes a key on Xcel's consent page, and the token gets that key's partner and mode (`mail:test` or `mail:live`). Never ask a person to paste a key into the chat. Start in the sandbox; it never prints, mails or spends. A person without a key gets sandbox access at https://www.updash.com/developers/agents and creates the key in the console; every new sandbox has the sample dealer `sandbox-dealer-1`. - Customer rows never go in a request body, query string or tool argument. PUT the CSV to the signed `upload_url` you are given. Responses carry ids, counts, reason codes and row numbers only. - Brand values (logo, colors, phone, dealer name and address, disclosures) and the FCRA notices are locked in a creative. Do not send them in a creative request; it is refused with `locked_field`, as is any copy key starting with `brand`, `dealer`, `recipient` or `fcra`. - You own each dealer's profile (name, address, phones, hours, links, logo, colors, lead routing): `POST /v1/dealers`, `GET`/`PUT`/`PATCH /v1/dealers/{id}/profile` with `If-Match` (412 `version_conflict` when stale), `POST /v1/dealers/{id}/logo`, `POST /v1/dealers/import` (CSV); MCP `list_dealers`, `get_dealer_profile`, `upsert_dealer`, `update_dealer_profile` (needs `expected_version`), `upload_dealer_logo`. Approvers, disclosures, the license number, spend caps and live ordering are Xcel's (`xcel_managed_field`). Test and live keys share dealers, so every dealer write with a test key only validates: it answers `"validated_only": true` (REST header `X-Xcel-Validated-Only: true`) and saves nothing; make the change with a live key. A creative prints the profile version it was made with: after a change, `generate_creative` again (then proof, quote, approval). A new dealer is not `live_ready` until Xcel registers its approver; live approval and submit answer `no_registered_approver` until then. `dealer.updated` webhooks carry the version and changed field names only. - Each template lists typed copy slots with a `sample`; the samples pass preflight, so start from them. Optional slots with a `default` are filled with it when you leave them out. Omit `cta_phone` and the dealer's main phone (`phones.main` in its profile) prints. A `brand_logo_unreachable` proof warning means the dealer's logo will print as a placeholder box; upload the logo (`POST /v1/dealers/{id}/logo`) and generate the creative again. - If copy quotes a payment, rate, term or down payment, fill `offer_disclosure` with the full terms. Pre-approval language makes the piece a prescreened credit offer that needs the FCRA notices, `firm_offer` and `prescreen_cra` (only `letter_8_5x11_equity` carries the notices). These checks are pattern checks, not legal review: never call a piece compliant, and never invent credit terms. - A `brief` on `generate_creative` (template creatives only) has Claude draft the copy slots your `copy` leaves out. Never put customer data in it. State credit or lease figures only in `brief.offer_terms` (the server then writes the disclosure). Send phones, disclosures, hours and signer names yourself. Read `ai_copy`: `needs_edit` means a slot was removed or preflight failed. Without AI copy configured a brief gets 503 `ai_copy_unavailable`; send your own `copy` instead. - You cannot approve spend. A human approves on the review page. Live: Xcel emails the link to a registered approver and you never receive it. Sandbox: the link comes back as `review_url` with `sandbox_only: true`; give it to the human, do not open it yourself. - `submit` needs an `Idempotency-Key`: one per order (scoped to your partner and the key's mode), reused only to retry the same request. - Your own `html` may not run or load anything: no scripts, event handlers, remote or relative URLs, `@import`, `@media print`, `@page` or `:has()`. They are removed and each is a preflight error. Inline images as `data:image` URLs and use `{{brand.logo}}`. - Where proofs print to PDF, `request_approval` binds the approval to the exact print file (409 `proof_pdf_missing` without one; `proof_pdf_changed` if it changes). `render_proof` is limited per partner per hour (429 `proof_render_rate_limited`): render once the creative and audience are final. - One PUT per audience upload URL: to correct a file, call `upload_audience` again for a new URL. - Give every audience row an opaque `external_id` (your CRM or CDP id, at most 128 characters; never an email, phone or name: one that looks like an email or phone number is blanked with the warning `external_id_contact_info_blanked`). Results come back per lead keyed by it and by the row key the API assigns, and per-lead results are deleted 365 days (by default) after the order closes. Do not send a `ResponseCode`, `response_code` or `row_key` column: they are reserved. - A new creative or audience sends the campaign back to `draft` and voids the proof, quote and approval. Render the proof, get a quote and request approval again. - Every REST error and MCP tool error is `{"error":{"code","message","fix","field"}}`. Do what `fix` says. Most state-changing responses have `next_step`; follow it. When one has none (audience complete, submit), read `next_step` from `GET /v1/campaigns/{id}` or `get_campaign_status`. - USPS statuses (`at_usps`, `in_postal_network`, `delivery_estimated`, `completed`) and `delivery_percent` rest on processing scans, not proof of delivery. - `get_campaign_results` returns `results` (totals) and `leads` (pages via `leads_after` = `leads.next_after`, `leads_limit` up to 500): responses and matched sales or service per `external_id`, never names or addresses. Live results exist only after Xcel links the order to its tracking (refreshed every 6 hours); sandbox results are simulated (`simulated: true`) from `in_postal_network` on. Matched `gross_cents` and `revenue_cents` (integer cents) are the dealer's confidential DMS figures; a match is evidence, not proof the mail caused the sale. - Pilot prices are placeholders (`placeholder_pricing: true`). Never present them as prices. - REST only, no MCP tool: listing campaigns (`GET /v1/campaigns`), webhooks (`/v1/webhooks`), the dealer CSV import, profile versions and PUT, and the sandbox production simulator (`POST /v1/test/campaigns/{id}/advance`). ## Docs - [Overview](/docs/index.md): what the API is, Phase 1 status, the mental model, the campaign lifecycle, test vs live - [Quickstart](/docs/quickstart.md): sandbox key to a simulated submission, step by step with curl and real responses - [Concepts](/docs/concepts.md): partners, dealers and profiles, brand values, campaigns and their state machine, versions, quotes, approvals, idempotency, spend caps, results and attribution - [Dealers](/docs/dealers.md): creating dealers, the profile fields and versions, If-Match, logos, the CSV import, Xcel-managed fields, the live gate, UpDash sync - [Products](/docs/products.md): the four launch products, geometry, design rules, mail classes, values not yet confirmed by the printer - [Audiences](/docs/audiences.md): CSV columns and `external_id`, upload flow, dedupe, suppression, reconciliation report and reason codes, row keys, CASS/NCOA, retention, PII rules - [Creative](/docs/creative.md): the four templates and their slots, merge fields, locked brand inputs, every preflight issue, compliance checks (pattern checks, not legal review), address fitting, HTML proofs - [Quotes and approvals](/docs/quotes-and-approvals.md): how quotes are built, small runs, the approval flow live and sandbox, the review page, every submit refusal - [Webhooks](/docs/webhooks.md): event types and payloads (including `dealer.updated`), signature verification in Node, Python and PHP, retries, de-duplication by `X-Xcel-Event-Id` - [Partner console](/docs/console.md): the website for your staff at /console: self-serve sandbox access, email sign-in, test and live mode, roles and dealer scope, orders and what each waits on, masked per-lead data with logged reveals and exports, the dealer profile editor, API keys, webhooks, team and usage - [Errors](/docs/errors.md): the error shape and every error code with its fix ## Agents - [MCP server](/docs/mcp.md): endpoint and auth (OAuth or API key header), connecting Claude, Claude Code and other clients, every tool, safety rules - [API reference](/docs/api-reference.md): every REST endpoint with auth and retry behavior ## Security - [Security](/docs/security.md): keys, OAuth for the MCP endpoint, scoping, PII, results and dealer data, the partner console, retention, approval guarantees, webhook safety, FTC Safeguards notes ## Optional - [Glossary](/docs/glossary.md): IMb, CASS, NCOA, PAF, presort, commingling, mail classes, co-op, FCRA prescreen, CDP and other terms