Go ahead with a draft or pending action; applies it when a person approves (or a rule allows), else submits it for approval.
From an API key / agent: a draft becomes pending_approval (a person approves at approve_url) unless an
auto-apply rule clears it, in which case it is applied. A pending_approval action needs a person unless the
org enabled agent approval (403 otherwise). From the dashboard: applies it. The diff is recomputed before
applying; 409 conflict when the account changed since the dry run (force: true applies the new diff).
The result is applied (1 credit) or failed with error (free).
Request
curl -X POST "https://api.appgoat.ai/v1/actions/123/approve" \
-H "x-api-key: $APPGOAT_API_KEY" \
-H "content-type: application/json" \
-d '{"force":false}'const res = await fetch("https://api.appgoat.ai/v1/actions/123/approve", {
method: 'POST',
headers: {
"x-api-key": process.env.APPGOAT_API_KEY,
"content-type": "application/json",
},
body: JSON.stringify({"force":false}),
});
const { data, meta } = await res.json();import os, requests
res = requests.post(
"https://api.appgoat.ai/v1/actions/123/approve",
headers={"x-api-key": os.environ["APPGOAT_API_KEY"]},
json={"force":False}
)
body = res.json()Path parameters
idstringrequiredAction id (
act_…).Request body
JSON.
forcebooleanResponse
200 with data: Action. See the envelope for meta.
idstringtypeActionTypeconnection_idstringstatus"draft" | "pending_approval" | "approved" | "applied" | "failed" | "rejected"paramsobjectdiffDiffguardrailGuardrailresultanyWhat the provider returned when applied.
errorstring | nullcreated_bystringapi, mcp, playbook:<name> or dashboard.
approved_bystring | nulluser:<id>, auto (rule) or api.
rejected_reasonstring | nullcreditsintegercreated_atstringapplied_atstring | nullexpires_atstring | nullapprove_urlstring | nullDashboard page where a person approves it.
auditobject[]Only on GET /actions/{id}.
audit[].eventstringaudit[].actorstringaudit[].atstringaudit[].detailobject | nullExample
{
"data": {
"id": "act_Qm3xXk2Lw9PaZr0cVb",
"type": "review.reply",
"connection_id": "string",
"status": "draft",
"params": {},
"diff": {
"target": {},
"changes": [
{}
],
"summary": "string",
"notes": [
"string"
]
},
"guardrail": {
"status": "passed",
"score": 4.5,
"checks": {},
"warnings": [
"string"
],
"checks_failed": [
{}
],
"model": "string"
},
"result": null,
"error": "string",
"created_by": "string",
"approved_by": "string",
"rejected_reason": "string",
"credits": 1,
"created_at": "2026-10-22T10:00:00Z",
"applied_at": "2026-10-22T10:00:00Z",
"expires_at": "2026-10-22T10:00:00Z",
"approve_url": "string",
"audit": [
{
"event": "string",
"actor": "string",
"at": "2026-10-22T10:00:00Z",
"detail": {}
}
]
},
"meta": {
"credits_used": 1,
"credits_remaining": 9981,
"request_id": "req_123"
}
}Status codes
200
The action after approval (pending_approval, applied or failed).
Missing or invalid API key.
insufficient_credits (the call costs more than you have left; nothing was done) or plan_limit (a feature of a higher plan).403
Needs a person (approve in the dashboard at
error.approve_url).404
not_foundNot found.
409
The state does not allow this (already applied, expired, connection read-only or invalid, account changed since the dry run).
429
rate_limitedToo many requests per second for your plan.