Echo/Docs

CLI command

approvals

The approval queue: irreversible actions Echo's automation may not take on its own wait here until a reviewer approves or rejects them.

Invocation#

Shell
echo-cli approvals list [--status pending|running|executed|failed|rejected]
echo-cli approvals request --action <action> --payload <json> --reason <text> --by <name>
echo-cli approvals approve <request-id> --by <name> [--note <text>] [--payload <json>]
echo-cli approvals reject <request-id> --by <name> [--note <text>]
echo-cli approvals policy list
echo-cli approvals policy set --action <action> --reason <text> --by <name>
echo-cli approvals policy unset --action <action>

Inputs and output#

list writes { requests, actions }: every request newest first, or only those of --status — id, requested_at, requested_by, action, payload, reason, status, decided_by, decided_at, note, finished_at, result and error — and, for each action the queue can hold, what running it does. request writes the queued request, pending. approve writes the request as it finished: executed with the action's result, or failed with its error; with --payload the request runs with that payload instead of the one it was queued with — checked as a request's payload is — and keeps it, so a held comment is approved with its text corrected. reject writes the request rejected; its action never ran, and what it held is settled (a held review is closed as rejected with the note). policy list writes the approval policies: action, reason, set_by and set_at; policy set and unset write the policy set or removed.

State effect#

Every verb calls Echo Web's authenticated /api/automation/approvals or /api/automation/approval-policies operation with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the dashboard's Approvals screen shows the same queue with each pending request's payload editable beside Approve and Reject and the same policies under Approval policies, Echo Desktop's GEO screen shows the queue with Approve (with an amended payload when one is typed) and Reject (its Approval queue section) and the policies, and echo-mcp lists the queue (echo_list_approvals) and the policies (echo_list_approval_policies) and queues into it (echo_request_approval) but cannot approve or set a policy. The queue holds eight actions. publish_blog_post, with the payload { "post_id": "<blog post id>" }, publishes the post and revalidates the landing that serves it, the same publication the blog admin's Publish button runs. send_campaign_message, with the payload { "campaign_id": "<id>", "recipient_id": "<id>", "step": <step or null> }, is what the campaign job queues when its channel is at its autonomy budget or has none (see autonomy): it is queued once however many ticks find the send held, and once approved it sends to that recipient only if the recipient is still pending or scheduled at that step and the campaign is still active, its audit row naming the approval and who approved it. publish_announcement, with the payload { "entry_id": "<changelog entry id>" }, is what echo-cli changelog draft queues beside the release notes post: once approved it announces the entry in the product's feed and emails every subscriber, published in the approver's name, and keeps the announcement id, its title and how many emails were sent and failed (see announcements). send_newsletter sends a queued newsletter draft (see newsletters). dispatch_platform_action is what an approval policy queues: policy set --action names a dispatched account action (a post, comment or message as Echo's action history names it, such as reddit_post, or a spend such as a vendor's <slug>_topup) and from then on every dispatch of it — by the agent, a routine, a sweep, an account action route or the campaign sender — is queued instead of run, with the policy's reason, the account, the params and any schedule still ahead in its payload; the same action asked again finds its request already waiting; the caller answers HTTP 202 naming the request. Once approved it is dispatched exactly once through the same path, still counted against the community rates, carrying the request it runs under; a dispatch carrying a request that is not running for that action is refused. change_ad_campaign, with the payload { "campaign_id": "<ad campaign id>", "change": { "status": "PAUSED" } or { "daily_budget": <amount> }, "asked_by": "<who asked>" }, is what an ad budget rule's pause or scaled budget and an anomaly watch's pause queue instead of changing the campaign once policy set --action change_ad_campaign holds ad spend changes: the rule records the change unapplied with the request it waits as, and once approved the change is made through Stado's paid-ads integration exactly as the rule would have made it. publish_caption_shoot, with the payload { "series_id": "<caption shoot>" }, is what the caption publishing job queues when the needher-captions channel is at its autonomy budget or has none (see autonomy): once approved it publishes every frame of the shoot still generated in the caption manifest to the NeedHer ingest. submit_reviewed_action, with the payload { "action_log_id": "<action log id>", "text": "<the text to post>" }, is an account action a trajectory held for review (an action log in pending_review, such as a promote comment the model wrote): the hourly /api/cron/review-queue queues each such log once with the text the trajectory proposed and why it was held, and recorded each tick that queued one; once approved — with the text as proposed, or corrected with --payload or the payload field — the action runs again on Weles with that text and the log records the run it started; rejected, the log is closed as rejected with the note. The queue is the one place held reviews are decided; the former /reviews page and /api/reviews routes are gone. policy unset lets the action run again; requests already queued stay to be decided. Of two identical requests only one can wait at a time. Approval moves a request from pending to running in one conditional write, so of two reviewers deciding at once only one runs it; the action runs once and the request keeps its result or error. --by names who acts for an internal caller; a reviewer signed in to the dashboard is named by their account.

Refusals#

  • A wrong invocation is refused as a usage error before anything is sent: "approvals takes list [--status <status>]; request --action <action> --payload <json> --reason <text> --by <name>; approve <request-id> --by <name> [--note <text>]; reject <request-id> --by <name> [--note <text>]; policy list; policy set --action <action> --reason <text> --by <name>; policy unset --action <action>; got <word>", "approvals policy takes policy list; policy set …; policy unset …; got <word>", "approvals <verb> does not take <flag>; it takes …", "<flag> needs a value", "approvals request needs <flag>" for each of --action, --payload, --reason and --by, "approvals policy set needs <flag>" for each of --action, --reason and --by, "approvals policy unset needs --action", "approvals approve needs --by" (and reject), "approvals approve needs a request id, as approvals list prints it" (and reject), and "--payload is not JSON: <cause>".
  • Echo Web's refusal is printed as "Echo Web refused <method> /api/automation/approvals… with HTTP <status>: <its answer>": a bad request with "status must be one of pending, running, executed, failed, rejected, got \"<value>\"", "action is required: one of publish_blog_post, send_campaign_message, publish_announcement, send_newsletter, dispatch_platform_action, change_ad_campaign, publish_caption_shoot, submit_reviewed_action", "action must be one of …; got <action>", "the payload of <action> is not what it takes: <field> <problem>", "reason is required: why this action waits for a reviewer", "<field> is required: an internal caller has no signed-in user, so it names who is acting", "decision must be one of approve, reject; got <value>" or "note, when sent, must be non-blank text"; not found with "no approval request has id <id>"; a conflict with "approval request <id> is <status> (<what that means>); only a pending request can be decided". /api/automation/approval-policies answers a bad request with "action is required: the dispatched account action that needs approval, such as reddit_post" or "reason is required: why the action waits for a reviewer before it runs", and not found with "<action> is under no approval policy". An approved change_ad_campaign that the platform refuses fails with the refusal changeCampaign names, such as "campaigns on <platform> cannot be changed through Stado's paid-ads integration", "ad account <id> is not active" or "the platform refused the change: <cause>".
  • A dispatch an approval policy holds answers HTTP 202 with "<action> needs approval (<reason>); it waits as approval request <id> until echo-cli approvals approve <id> dispatches it" and approval_id; one carrying a request that is not running for that action answers HTTP 409 with "no approval request has id <id>; <action> was not dispatched", "approval request <id> holds <action> …, not <action>; nothing was dispatched" or "approval request <id> is <status>; <action> is dispatched only while its approval runs".
  • An approved request whose action fails is not refused: approve writes it failed with the action's error, and the action is not run again. publish_blog_post fails with "no blog post has id <id>; it was deleted after it was queued"; send_campaign_message with "no campaign has id <id>; nothing was sent", "campaign <id> is <status>; a held send runs only for an active campaign, so nothing was sent", "campaign <id> has no recipient <id>; nothing was sent", "recipient <id> is <status> at step <step>, and the held send was for step <step>; nothing was sent", "the send was not made: <why the send step deferred it>" or "the send failed: <cause>"; publish_announcement with the refusals of echo-cli announcements publish, such as "changelog entry <id> was already announced as <announcement id> at <time>".
  • An amended approval is refused before anything runs with "--payload is not JSON: <cause>" as a usage error, and by Echo Web with "the amended payload of <action> is not what it takes: <field> <problem>" or "payload amends what an approval runs; a rejection runs nothing, so it takes none" as a bad request. submit_reviewed_action fails, once approved, with "action log <id> does not exist; it was removed after it was queued" or "action log <id> is <status>, not pending_review; it was decided outside the queue"; a rejection whose log cannot be closed answers "approval request <id> is rejected, but what submit_reviewed_action held could not be closed: <cause>", the request staying rejected.