Appearance
ad-runs
29 endpoints.
GET /api/v1/ad-runs
List Runs
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
status | query | no | ||
limit | query | no | integer | |
offset | query | no | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs
Create Run
Request body
json
{
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Name"
},
"mode": {
"type": "string",
"title": "Mode",
"default": "autopilot"
},
"brand_kit_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Brand Kit Id"
},
"account_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Account Id"
},
"page_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Page Id"
},
"instagram_actor_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Instagram Actor Id"
},
"inputs": {
"additionalProperties": true,
"type": "object",
"title": "Inputs"
},
"reference_ids": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Reference Ids"
},
"start": {
"type": "boolean",
"title": "Start",
"default": true
}
},
"type": "object",
"title": "RunCreate"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/references/url
Add Reference Url
Attach a web page, and read it unless the caller says not to yet.
The read is synchronous when it happens — the screen shows what was found before you commit to the campaign, and the fetch is bounded (20s) as is the model call. analyse=false stores the row pending instead; see _analyse_pending.
Request body
json
{
"properties": {
"url": {
"type": "string",
"title": "Url"
},
"purposes": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Purposes"
},
"purpose": {
"type": "string",
"title": "Purpose",
"default": "brand"
},
"ad_run_id": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Ad Run Id"
},
"analyse": {
"type": "boolean",
"title": "Analyse",
"default": true
}
},
"type": "object",
"required": [
"url"
],
"title": "ReferenceUrl"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/read-blocks
Read Blocks
Read an advertiser's packages and features OUT of a picture, for the form.
2026-09-10, owner: "for the packages and features, allow for attachment of an image and it should interpret those image and fill in the boxes".
RUN-LESS and STATELESS, and both matter. Run-less because this is pressed on the New Ad Creative form before a run exists — the same reason /references/upload accepts a null ad_run_id. Stateless because nothing here is a reference: the file is read, answered from, and dropped. It is not saved to disk, not written to ad_references, and never reaches the brief or the image generator. What the advertiser keeps is what lands in the fields, which they then edit — so a misread costs a correction rather than a wrong price on a published ad.
Storing it was the obvious alternative and is the wrong one twice over: a pricing screenshot attached as a reference would be read a SECOND time by the brief as material to design from, and "the format and design they want matched" is the last thing a screenshot of a competitor's price table should mean.
Never 500s on a bad read — see :func:ad_creative_design.read_blocks_from_image. An empty answer means "nothing found in that image", which the form says.
200 — Successful Response
json
{
"additionalProperties": true,
"type": "object",
"title": "Response Read Blocks Api V1 Ad Runs Read Blocks Post"
}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/references/upload
Add Reference Upload
Attach a picture (or a PDF), and look at it unless told to wait.
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/{run_id}/references
List References
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}PATCH /api/v1/ad-runs/references/{ref_id}
Patch Reference
Change what an attached reference is for — the pencil on the intake panel.
2026-09-07, owner: "all the links and upload should be pasted immediately with a small pencil to edit what the reference is for if needs to be changed". Attaching no longer asks the question up front, so this is where it is answered when the default is wrong.
Changing the purposes invalidates the analysis. The analyser is TOLD what it is looking for and asks different questions of the same page for "our business" than for "the colours" (see reference_analyzer.purpose_brief), so keeping an analysis written against the OLD purpose would be worse than having none — it reads as an answer and is an answer to something else.
So the row is re-read here, unless the caller is a form that has not been submitted yet and says analyse=false — there it goes back to pending and _analyse_pending picks it up on submit.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
ref_id | path | yes | integer |
Request body
json
{
"properties": {
"purposes": {
"items": {
"type": "string"
},
"type": "array",
"title": "Purposes"
},
"analyse": {
"type": "boolean",
"title": "Analyse",
"default": true
}
},
"type": "object",
"required": [
"purposes"
],
"title": "ReferencePatch"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}DELETE /api/v1/ad-runs/references/{ref_id}
Delete Reference
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
ref_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/references/{ref_uuid}/file
Get Reference File
Serve an uploaded reference back for preview. Addressed by uuid and tenant-checked, exactly like the generated assets below.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
ref_uuid | path | yes | string |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/{run_id}
Get Run
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}PATCH /api/v1/ad-runs/{run_id}
Patch Run
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
Request body
json
{
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Name"
},
"mode": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Mode"
},
"account_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Account Id"
},
"page_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Page Id"
},
"instagram_actor_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Instagram Actor Id"
},
"stage_data": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Stage Data"
}
},
"type": "object",
"title": "RunPatch"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}DELETE /api/v1/ad-runs/{run_id}
Delete Run
Soft-delete. The assets on disk stay until a sweep removes them — a run deleted by accident an hour before a campaign goes out is recoverable.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/advance
Advance Run
Continue the chain from wherever it stopped.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/{run_id}/progress
Run Progress
What an interrupted or failed run has already saved.
Read by the process dialog to offer Continue — and to say what continuing keeps ("4 of 6 images already drawn") — instead of only Re-run, which starts the step again.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/continue
Continue Run
Pick an interrupted or failed run up from where it stopped.
2026-09-14, owner: "if the process is interrupted at any point in time, the progress is saved and I can click the view process button and continue from where I left off instead of needing to restart".
Unlike /rerun this does not start the step again: the stage that was interrupted runs with resume=True and reuses everything it saved (see ad_run_stages.saved_progress), and the chain then carries on exactly as /advance would. A run whose worker is still alive — touched within the last heartbeat window — is left alone rather than started twice.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/stages/{stage_name}/rerun
Rerun Stage
Re-run exactly one stage — "regenerate the images" — without re-deriving anything earlier.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
stage_name | path | yes | string |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/stages/{stage_name}/redo-after
Redo After Stage
Throw away every stage AFTER stage_name and derive them again.
The companion to editing a stage's output by hand (2026-09-03, owner: "as a step is completed, one should be able to edit the outcomes ... the next step would take the updated, edited info"). An edit reaches the steps that have not run yet all by itself — every stage reads the run — but a step that has ALREADY produced something was derived from the version just replaced, and nothing would otherwise ever revisit it. This is the button that says "and redo what came after".
Deliberately NOT automatic on PATCH. Re-deriving the creative stage renders three fresh 2K images: that costs real money and about a minute, and an edit is very often a wording fix that changes nothing downstream. The module says which later steps are now stale and leaves the decision to the person.
The run itself is untouched up to and including stage_name — this is the opposite of the intake fork, which is why intake is not accepted here.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
stage_name | path | yes | string |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/{run_id}/events
Run Events
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
limit | query | no | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/creatives/{variant_id}/refine
Refine Creative
One turn: edit the variant's current image and append it to the thread.
The edit is applied to the image the LAST turn produced, not to the original render, so changes accumulate the way the person asking for them expects them to.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
variant_id | path | yes | string |
Request body
json
{
"properties": {
"message": {
"type": "string",
"title": "Message",
"default": ""
},
"reference_ids": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Reference Ids"
},
"aspect_ratio": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Aspect Ratio"
},
"deck": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Deck"
},
"attachment_ids": {
"items": {
"type": "integer"
},
"type": "array",
"title": "Attachment Ids"
}
},
"type": "object",
"title": "RefineRequest"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/creatives/{variant_id}/use-turn
Use Refined Turn
Swap a turn's image back in as the variant's creative, and select it.
The variant's OTHER ratios are re-framed from the accepted image rather than left as they were: they were alternates of a design that has since been edited, and publishing or previewing one of them would show artwork nobody approved. A re-frame that fails drops that ratio instead of blocking the accept — the first image is the one that publishes (see ad_publish).
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
variant_id | path | yes | string |
Request body
json
{
"properties": {
"turn_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Turn Id"
}
},
"type": "object",
"title": "UseTurnRequest"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/creatives/upload
Upload Creative
Use this picture as the ad, instead of generating one.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/creatives/adopt
Adopt Creative
Carry the image chosen at the end of a creative run into a campaign.
The bytes are COPIED into a new asset owned by this run rather than the row being re-pointed. Two runs then hold two independent assets: deleting the creative run, or refining its image afterwards, cannot change what a campaign already published, and the campaign's provenance still names where the picture came from (the event log, and inputs.from_run_id).
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
Request body
json
{
"properties": {
"from_run_id": {
"type": "integer",
"title": "From Run Id"
},
"variant_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Variant Id"
}
},
"type": "object",
"required": [
"from_run_id"
],
"title": "AdoptCreativeRequest"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/assets/{asset_uuid}
Get Asset
Serve one generated asset, whole or as a thumbnail.
Authenticated and tenant-checked, and addressed by the row's uuid rather than by a sequence, so the URLs are neither walkable nor guessable. This is why these files get no nginx alias: they are unreleased campaign artwork.
w asks for the small version — see ad_run_store.thumbnail. The Kanban gallery draws its cards at about 250px and was pulling whole 2-9 MB creatives to do it, which is what made that view slow to open. An unsupported width is ignored rather than refused: a thumbnail is an optimisation, and the honest fallback is the picture itself.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
asset_uuid | path | yes | string | |
w | query | no | Serve a cached thumbnail this many pixels wide instead of the original (240, 480 or 960). | |
intent | query | no | download | copy — a person keeping this image; recorded for creative_learning. |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/fixes/{code}
Apply Fix
Apply one of :mod:ad_publish_fixes' value changes, re-check, and publish.
publish=false stops after the re-check (2026-09-09, owner: "add a quick fix button to any error that can be easily changed, it should be pressed, load and have the issue fixed so publish is one step away"). That is the Pre-flight step's press: somebody reading the checks wants the finding cleared and the checks re-run, with Publish left as the deliberate next step. The Publish step's own button keeps the default — there, the publish has already been asked for and refused.
2026-09-08, owner: "when clicking that button, it should make the change on the campaign it is creating, not a new campaign and publish the edit". The whole value of a one-press fix is that it is one press, so this does the entire sequence a person would otherwise have to walk by hand:
- change the value;
- drop the last pre-flight — it ran against the value just replaced, exactly as a hand edit drops it (see :func:
patch_run) — and clear the publish failure the fix answers; - run pre-flight again, INLINE. Dispatching it would return here with nothing to publish yet, and there would be no second press coming;
- publish, if pre-flight is clear.
On the campaign it is already creating, never a second one. :func:ad_publish.publish_run skips every step whose id the run already holds, so a run that got as far as a campaign before Meta refused the ad set resumes at the ad set — the campaign id it recorded on the way is what makes that the natural behaviour rather than a special case.
Slower than the endpoints around it (a pre-flight's live checks, then the publish itself) because it IS three actions. It answers with what happened at each of them rather than with a bare ok.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer | |
code | path | yes | string | |
publish | query | no | boolean | Publish if the re-check comes back clear. False stops after the re-check, which is what the Pre-flight step's button asks for. |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/publish
Publish
Create everything on Meta, PAUSED. Never activates anything.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
Request body
json
{
"properties": {
"variant_ids": {
"items": {
"type": "string"
},
"type": "array",
"title": "Variant Ids"
},
"skip_preflight": {
"type": "boolean",
"title": "Skip Preflight",
"default": false
}
},
"type": "object",
"title": "PublishRequest"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/go-live
Go Live
Activate campaign -> ad set -> ad. THIS STARTS SPENDING.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/pause
Pause
Stop spending, now.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}POST /api/v1/ad-runs/{run_id}/clone
Clone Run
Copy the inputs, brand kit and (optionally) strategy into a fresh draft.
The GENERATED stages and the Meta ids are deliberately NOT copied: new images and new copy are the point, and carrying meta_ids across would make the clone believe it already owns a campaign it has never created.
This is also how an unfinished campaign's intake is EDITED (2026-09-03, owner: "if I select a draft run, one that is not complete as yet, I should be able to go to the intake and make an edit and it should auto create a new run with the same run name and -copy after the name, then process it as a new run from the intake"). The Intake module sends the edited answers as inputs and the source run is left untouched — which is the point: a half-built campaign is work, and changing the question it was built from must not silently invalidate the brief, strategy, images and copy already sitting on it. The fork starts again from intake instead, under <name>-copy.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
Request body
json
{
"properties": {
"name": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Name"
},
"keep_strategy": {
"type": "boolean",
"title": "Keep Strategy",
"default": true
},
"mode": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Mode"
},
"start": {
"type": "boolean",
"title": "Start",
"default": false
},
"inputs": {
"anyOf": [
{
"additionalProperties": true,
"type": "object"
},
{
"type": "null"
}
],
"title": "Inputs"
},
"account_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Account Id"
},
"page_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Page Id"
},
"instagram_actor_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Instagram Actor Id"
},
"reference_ids": {
"anyOf": [
{
"items": {
"type": "integer"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Reference Ids"
}
},
"type": "object",
"title": "CloneRequest"
}200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}GET /api/v1/ad-runs/{run_id}/performance
Performance
What this run actually did, against what the strategy predicted.
Spend and results come from the synced InsightsDaily rows for the campaign this run created; the AI cost comes from the run's own event log, so the model spend is visible next to the ad spend rather than being invisible.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
run_id | path | yes | integer |
200 — Successful Response
json
{}422 — Validation Error
json
{
"properties": {
"detail": {
"items": {
"properties": {
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"type": "array",
"title": "Location"
},
"msg": {
"type": "string",
"title": "Message"
},
"type": {
"type": "string",
"title": "Error Type"
}
},
"type": "object",
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError"
},
"type": "array",
"title": "Detail"
}
},
"type": "object",
"title": "HTTPValidationError"
}