Approval
How the first-N-blocked gate works, where it's enforced, and what happens when a partner ignores it.
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:
- Project policy.
requires_approval: booleanandfirst_n_posts_blocked: int. Policy lives on the project row and applies to every content item the project generates. - Content status. Every content item has an
approval_statusofnot_required|pending|approved|rejected. New containers on an approval-required project start atpending; the rest default tonot_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 scheduleApproval 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"
}approveflipsapprovalStatustoapproved, stampsapprovedAtandapprovedBy(the API key id that approved), persists thenote, and incrementscurrentBlockedCounton the project.noteis optional, max 1024 chars.rejectflipsapprovalStatustorejected, stampsrejectedAtandrejectedBy(API key id), persistsreason.reasonis required, 1–1024 chars. Rejected containers stay in the project for audit — they don't get deleted. To regenerate after a rejection, callPOST /v1/content/:id/regeneratewith 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.