Apply a sendops.json bundle, dry run by default

Search Documentation

Search across all developer documentation

bundle

Apply a sendops.json bundle, dry run by default

POST /v1/import
Auth required

Applies a bundle — the same shape GET /v1/export produces — through the ordinary authoring services. Every object goes through the method the dashboard and MCP call, so optimistic concurrency, version snapshots with actor identity, save-time segment evaluation and inline SES deploy all apply.

dry_run defaults to true. A call that omits it returns the plan and writes nothing. That default is deliberate: this route is reached by pipelines, and a misconfigured job should print a plan rather than rewrite an organization.

prune defaults to false. With prune=true, objects the org has and the bundle omits are archived (segments, lists, topics, workflows) or deleted (attributes, activity properties) — and an attribute a remaining segment or workflow still references is skipped with referenced_by naming what blocked it, never deleted. Pruning applies only to sections the bundle actually declares: a templates-only push never archives an audience. Images are never pruned, because mail already delivered renders from the URLs they back.

Applicable or nothing. If any object fails validation the response carries the full plan with applicable: false and every diagnostic positioned by line and column, and NOTHING is written — not even the objects that passed.

Idempotent. Applying the same bundle twice yields an all-unchanged plan and writes nothing on the second run.

Apply order, which is also the order the plan lists objects in: attributes → activity properties → lists → segments → topics → images → templates → workflows. Each step's validation needs the previous step's rows; images precede templates because a template deploys with its image URLs embedded. A workflow created by an import lands as a draft; an existing active workflow updated by one stays active.

Partial results are reported honestly. The services open their own transactions, so a failure part-way leaves the earlier kinds written. applied_until names the last kind that completed and error says what stopped it.

Body. Either a zip archive (Content-Type: application/zip, or any body starting with the zip magic number) or the JSON form. The JSON form may also carry dry_run and prune as top-level fields, for clients building one document; the query parameter wins when both are given.

Scopes. Each kind the bundle CONTAINS needs that kind's .manage scope (api.templates.manage, api.segments.manage, api.lists.manage, api.topics.manage, api.attributes.manage, api.activities.manage, api.workflows.manage, api.assets.manage). A bundle of templates alone needs only api.templates.manage; a missing scope is a 403 that names it.

Query parameters

dry_run boolean optional

Plan only, writing nothing. Defaults to true — an unparseable value is a 422 rather than a silent fallback.

prune boolean optional

Archive or delete objects the org has and the bundle omits, within the sections the bundle declares.

Request body

Content type: application/json

manifest object optional

The sendops.json object. Wins over a files["sendops.json"] string when both are present.

files object optional
images object optional

Binary files, standard base64. A data: URL prefix is stripped.

dry_run boolean optional

The query parameter's inline twin, for clients building one document. The query parameter wins when both are given.

prune boolean optional

As dry_run — the query parameter wins.

Responses

Errors follow the RFC 7807 problem format — see the error reference.

200 The plan, and on an apply the per-object results. 200 even when applicable is false: the plan is the answer to the request, and a bundle that does not apply is a result to read, not a malformed call. application/json
dry_run boolean required

What actually happened. A dry run never has applied true.

plan object required

Fields of plan:

applicable boolean required

False when any object failed validation. Nothing is then applied, including the objects that passed.

prune boolean required

Echoes the request's flag.

objects array<object> required

Every object the bundle touches, in the order an apply writes them.

Each entry in objects:

kind string enum required

One of: template, segment, list, topic, attribute, activity_property, workflow, image

key string required

The identifier within the kind — a template name, a segment key, an image path, or activity.property for an activity property.

path string optional

The bundle file this object came from. Absent for a prune, which is about an object the bundle does not contain.

action string enum required

unchanged writes nothing at all — no version snapshot, no SES deploy, no segment re-evaluation — which is what makes a push safe to run on every commit.

One of: create, update, unchanged, archive, delete, skip

reason string optional

Why a skip, and the basis of any action that is not obvious.

referenced_by array<string> optional

What blocks a prune. Present only on a skip.

diagnostics array<object> required

Validation failures on this object. Non-empty means the whole plan is inapplicable.

Each entry in diagnostics:

path string optional

The bundle-relative file the finding is about.

line integer optional
column integer optional
message string required
totals object required

Objects per action. An action with no objects is absent rather than zero.

diagnostics array<object> required

Bundle-level problems — a manifest warning, a declared file the bundle does not carry.

Each entry in diagnostics:

path string optional

The bundle-relative file the finding is about.

line integer optional
column integer optional
message string required
applied boolean required

True only when every step completed.

objects array<object> required

Per-object outcomes of an apply. Empty on a dry run.

Each entry in objects:

kind string required
key string required
action string required
reason string optional

Why the object was skipped, in prose — a scope the credential lacks, a prune that something still references. Absent for an object that was written.

referenced_by array<string> optional

What blocked a prune. Only present on a skip.

deploy_state string enum optional

The template's SES state after the write. pending on an update of an already-deployed template is correct: SES holds the previous body until the new one lands.

One of: pending, deployed, failed

member_count integer<int64> | null optional

The segment's membership after the save-time evaluation, and null when that evaluation was queued instead. Null is not zero.

requires array<object> optional

The outstanding-dependency block for kinds that have one.

Each entry in requires:

kind string enum required

The namespace key is looked up in. Branch on this; never parse detail.

One of: template, segment, list, topic, identity, channel, attribute, activity_property, asset

key string required

The identifier as the definition names it — a template name, a segment id or key, a from-address. Empty for a requirement about the ABSENCE of a reference: a send with no topic at all names no topic.

status string enum required

ok — resolves and is usable now. missing — nothing in this organization answers to that key. Correct the reference, or create the thing. not_ready — it exists, in a state that cannot be used yet. Usually a wait, and retrying the identical request is the move. Treating this as missing sends you hunting for a typo that is not there.

One of: ok, missing, not_ready

detail string required

The state, in the words that say which state — deploy_state=pending, member_count=null (evaluation queued). For display and logs; do not parse it.

fix string required

The call that clears this requirement, as an HTTP route where there is one and as a plain instruction where there is not (a template deploy is waited for, not called). Empty on an ok entry.

error string optional

The failure that stopped the import at this object.

applied_until string optional

The last kind that completed when an apply stopped early. The kinds before it are written; the kinds after it are not.

error string optional

Why the apply stopped.

401 Missing, malformed, or unknown API key application/problem+json
403 Either the credential lacks the required scope (code: invalid_scope), or it is bound to the test environment and this operation is irreversible (code: test_environment_forbidden). Branch on code: the first is fixed by granting the scope, the second only by using a live credential. See the "Live and test credentials" section of the API description. application/problem+json
413 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. application/problem+json
422 A query parameter, path value or body field failed validation. The body is a validation_failed Problem. When the refusal is about a reference — a segment key, template slug, topic or attribute name the organization does not have — it additionally carries validation_code, field, line/column, missing, candidates and next_step, so a client can correct the call without a second round of guessing. See ValidationProblem. application/problem+json
429 Per-org rate limit exceeded application/problem+json
500 Unexpected server-side failure. The code is internal_error. The request_id field can be quoted to SendOps support to investigate. application/problem+json