Overview
Sign a plan at the terms_fingerprint from plan_quote, under the brand terms a person at the brand accepted for this key, and get the Stripe Checkout link the brand or its finance person pays (card or Link up to $10,000, US bank account at any size). Only inside the key's mandate: a refusal names the limit (no mandate, expired, signing not allowed, terms not accepted, channel, plan maximum, monthly cap) and how much over, so you can take it to a person. A plan that changed since the quote is refused with terms_changed: quote again. A plan over the campaign's budget is refused with over_budget and its over_by_cents, always: nobody can approve an overage, the brand included, so remove a creator or ask a person at the brand to raise the budget, then quote again. Signing a signed plan returns its order and resumes payment; it never signs twice. Needs an idempotency_key. Scope: plans:approve. MCP tool: approve_plan.
- Endpoint
POST https://app.distribution.tech/api/v1/plans/{id}/approve- Scope
plans:approve- Changes data
- Yes, send an Idempotency-Key header
- MCP tool
- approve_plan
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | Yes | |
Idempotency-Key | header | string | Yes | 8 to 128 letters, digits or . _ : -. Retry with the same key and the same request to get the same answer; it never runs twice. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
terms_fingerprint | string | Yes | The terms_fingerprint from /plans/{id}/quote. |
confirm_over_budget_cents | integer | No | Retired and ignored if sent. No plan over its campaign's budget is approved, so nothing confirms an overage: remove a creator or raise the budget. |
{
"terms_fingerprint": "<string>",
"confirm_over_budget_cents": 0
}Request with curl
Set DISTRIBUTION_API_KEY to a key from Developers with the plans:approve scope, and each path variable to an id from an earlier answer. The body carries the required fields.
curl -X POST "https://app.distribution.tech/api/v1/plans/$PLAN_ID/approve" \
-H "Authorization: Bearer $DISTRIBUTION_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"terms_fingerprint": "<string>"
}'Response
200 OK. The body has these fields:
| Name | Type | Required | Description |
|---|---|---|---|
plan_id | string (uuid) | No | |
status | "approved" | No | |
approved_at | string (date-time) | No | |
already_signed | boolean | No | |
order | object or null | No | |
payment | object | No |
{
"plan_id": "<uuid>",
"status": "approved",
"approved_at": "<date-time>",
"already_signed": false,
"order": {},
"payment": {
"next": "checkout",
"pay_url": "<string>",
"options": [
{
"rail": "checkout",
"label": "<string>",
"clears": "<string>"
}
]
}
}Errors
| Status | When |
|---|---|
400 | A parameter is not valid: details.problems names each field and what it accepts. |
401 | No API key, an unknown one, or a revoked one. |
403 | The key lacks the scope, or the mandate refuses (mandate_refused, with the limit and how much over). |
404 | No plan, line or creator of yours has that id. |
409 | conflict (with a reason), idempotency_conflict, or in_progress (Retry-After). |
422 | unprocessable: the body is readable but a value cannot be used (a blank or too long name, a campaign that cannot be planned yet); details.problems names each field when there are several. |
429 | Too many requests for this key in the current minute (60); Retry-After and RateLimit-Reset say when to try again. On POST /campaigns and on POST /plans/current/lines when it starts a plan: the brand's 3 new campaigns in 24 hours are used (details.reason daily_campaign_limit); details.resets_at and Retry-After say when the limit reopens, a rolling 24 hours from the first of them, not a calendar day; archiving or deleting a campaign does not give its slot back. On GET /creators and GET /creators/{id}: a creator read limit is used (details.reason per_minute, daily_lists, daily_reveals, daily_seen or locked). daily_seen is your workspace's one pool of 300 creator views a day (300 different creators shown or opened since midnight UTC), shared by the app and every API key of the brand: details.scope workspace and details.limit 300; a creator already seen today, in the app or through any key, still opens and costs nothing. daily_lists is the day's list calls (each GET /creators page or creators_search is one; details.limit, 60): it is counted apart from creator views, resets at midnight UTC, and Retry-After says when. daily_reveals is the day's creator profiles opened (details.limit, 60). The other reasons are this key's own limit (details.scope key) or the brand's ceiling over all its keys (details.scope api). |
500 | internal: the request failed on our side. Retry it with the same Idempotency-Key. |
503 | unavailable: writes are not available right now (for an approval: payments are not open yet). |
Related endpoints
POST /plans/{id}/dates: Move a plan's datesPOST /plans/{id}/lines: Add a creatorGET /plans/{id}/quote: A plan as you sign itDELETE /plans/{id}/lines/{line_id}: Remove a creatorPOST /plans/{id}/lines/{line_id}/swap: Swap a creator
Get access
Keys are created in Developers once your brand is approved. Connect an agent to ask for access, then read the agent guide for the full flow.