Layers
Partner APIConcepts

Approval

How the first-N-blocked gate works, where it's enforced, and what happens when a partner ignores it.

View as Markdown

Approval is the checkpoint between "content generated" and "content leaves the building." It's a flag on each content item and a policy on each project. The API enforces the gate; you build the review UX.

The current model is deliberately small: a boolean on the project ("should this project require approval") and a counter ("how many posts per user does that apply to"). A planned trust-score path will let approved accounts graduate off review automatically. Both live on the same fields.

The gate

Two things decide whether a content item can be scheduled:

  1. Project policy. requires_approval: boolean and first_n_posts_blocked: int. Policy lives on the project row and applies to every content item the project generates.
  2. Content status. Every content item has an approval_status of not_required | pending | approved | rejected. New containers on an approval-required project start at pending; the rest default to not_required.

The gate triggers on POST /v1/content/:containerId/schedule (and on /publish, which is just "schedule with scheduledFor: now"). If the project requires approval, the schedule call is refused with 403 APPROVAL_REQUIRED until the container flips to approved.

requires_approval = true  AND
approval_status != 'approved'
        →  403 APPROVAL_REQUIRED on schedule

Approval is enforced at the schedule endpoint and at the just-before-publish check. Defense-in-depth on every platform publish path (Instagram Reels, TikTok drafts, Managed publish) is planned. If you're calling the publish pipeline directly via platform services, don't — go through the schedule endpoint so the gate runs.

firstNPostsBlocked is informational

Projects carry a first_n_posts_blocked field on their approval policy and a current_blocked_count that starts at 0. These are intended for your UI — for example, rendering "2 of 3 reviewed" to an end user so they know how many more posts will hit the queue before the gate naturally relaxes.

Today the server-side gate fires whenever requiresApproval: true and the container isn't approved — there is no automatic decay from the counter. To turn the gate off for a project, PATCH /v1/projects/:id/approval-policy and set requiresApproval: false. An automatic decay / counter-driven disable is on the roadmap for a later phase.

Approving and rejecting

POST /v1/content/:containerId/approve
{
  "note": "On-brand, clean caption"
}

POST /v1/content/:containerId/reject
{
  "reason": "Wrong influencer for this product"
}
  • approve flips approvalStatus to approved, stamps approvedAt and approvedBy (the API key id that approved), persists the note, and increments currentBlockedCount on the project. note is optional, max 1024 chars.
  • reject flips approvalStatus to rejected, stamps rejectedAt and rejectedBy (API key id), persists reason. reason is required, 1–1024 chars. Rejected containers stay in the project for audit — they don't get deleted. To regenerate after a rejection, call POST /v1/content/:id/regenerate with a revised brief.

Actor attribution: both endpoints stamp approvedBy / rejectedBy with the calling API key's id. If you're building a review UI with multiple human reviewers, persist the reviewer's identity in your own audit table keyed by the container id. The partner-API audit log ties the action to the API key that signed the request.

Rejection is a terminal decision for that container. Calling schedule or publish against a rejected container returns 409 CONTENT_REJECTED — the gate never opens for it, even if project policy later changes. To move forward, regenerate with a revised brief or create a fresh container.

Once scope enforcement ships, these endpoints will require content:approve. Today, org-scoped keys can call them directly.

Reading the policy

GET /v1/projects/:projectId/approval-policy
→ 200
{
  "requiresApproval": true,
  "firstNPostsBlocked": 3,
  "currentBlockedCount": 1
}

Patch the same shape with PATCH to change either field. Scope: content:approve.

What's deferred

A planned extension adds trust_threshold and current_trust_score to the policy. The threshold is a number; the score is computed from reviewer agreement and downstream engagement. When the score crosses the threshold, the project auto-transitions to continuous approval — no need to flip requires_approval manually.

If you want trust-based flow today, implement it in your own system and call PATCH /approval-policy to flip requiresApproval off when you're confident.

On this page