Skip to Content
API referencePOST /v1/actions
Open in Claude Open in ChatGPT

POST /v1/actions

The core action proxy. Every call your agent makes to a connected SaaS platform flows through this endpoint.

Most agents call this indirectly via the MCP read_platform, write_platform and delete_platform tools. Direct invocation is only needed when building a custom MCP server or integrating from a non-MCP host.

Request

POST /v1/actions HTTP/1.1 Host: api.agentvalet.ai Authorization: Bearer <agent-rs256-jwt> Content-Type: application/json
{ "platform": "slack", "endpoint": "/api/chat.postMessage", "method": "POST", "scope": "chat:write", "data": { "channel": "#general", "text": "Hello" }, "headers": {}, "reason": "Posting the daily standup summary" }
FieldTypeRequiredNotes
platformstringyesPlatform id from list_platforms (e.g. slack, gmail, stripe)
endpointstringyesThe SaaS endpoint path. Validated by the SSRF guard — no private/loopback addresses
methodstringyesGET / POST / PUT / PATCH / DELETE
scopestringyesThe scope the agent claims for this call. Must be granted in the dashboard permission matrix
dataobjectnoRequest payload forwarded to the SaaS
headersobjectnoExtra headers forwarded to the SaaS
connection_idstringnoTarget a specific connection when the platform has several. Omit to use the default
callerobjectnoThe person this call is for, when one agent serves many people: { "assertion": "<jwt>" } (verified against the org’s trusted issuers) or { "source": "...", "id": "..." } (asserted). Also accepted as the X-AV-Caller header
reasonstringnoJustification shown to the human approver and recorded on the approval and audit row

Success — 2xx

The upstream response wrapped in a discovery envelope. The HTTP status mirrors the upstream status:

{ "data": { "ok": true, "channel": "C123", "ts": "1715000000.000100" }, "_meta": { "capability": "agent.action", "timestamp": "2026-05-18T10:40:55.123Z", "next_actions": [ { "capability": "audit.query", "endpoint": "GET /v1/audit" }, { "capability": "agent.permissions", "endpoint": "GET /v1/agent/permissions" } ], "docs": "https://api.agentvalet.ai/v1/openapi.json", "correlation_id": "0f4c9a61-2b7d-4e3a-9c85-6d1e0a2b3f47", "audit_id": "5d0e2f7a-3c1b-4a9e-8f6d-0b2c4e6a8d10" } }

correlation_id is always present. audit_id is the audit row id (and the id of its signed receipt). connection appears when the proxy can say which connection served the call. If the audit write failed, _meta.audit_write_failed is true — the call still happened, so do not retry it.

A non-2xx upstream response is returned with the upstream status and body as-is, not wrapped.

If the org has PII redaction beta enabled and the agent has a per-platform visibility map, the data body is rewritten before the response is returned. The audit row records pii_detected, pii_categories, pii_redacted, and pii_record_count. See PII redaction.

Pending approval — 202 Accepted

When the agent’s grant for the platform has require_approval set (it applies to every call on that grant, reads included), or a policy or guardrail asks for approval:

{ "status": "pending_approval", "approval_id": "7c1e4b2a-9d3f-4e8a-b6c5-2f0a1d9e8b34", "audit_id": "5d0e2f7a-3c1b-4a9e-8f6d-0b2c4e6a8d10", "message": "Owner approval required" }

Your agent should poll GET /v1/approvals/:id until status is approved (with result populated) or denied / expired. The MCP platform tools do this long-poll for you for up to 50 seconds; after that it returns the 202 envelope and your agent should explain the wait to the user.

Approvals expire 10 minutes from creation. The owner is notified in the dashboard inbox, plus push (mobile) and delegate email on Team and above.

Error codes

All errors follow the common envelope. The error identifier is stable; where it is a sentence, the machine code is in reason or code (and in report_hint.suggested_payload.code).

StatuserrorCauseFix
400Secrets detected in request paramsRequest body matched the secrets scanner (AWS key, Slack token, etc.)Don’t embed credentials in data; use the platform’s own auth mechanism
400Endpoint not allowedSSRF guard rejected the endpoint (private/loopback)Use the platform’s public API endpoint
400invalid_caller / untrusted_caller_issuerThe caller value is malformed or its assertion can’t be verifiedFix the caller field or header
401Missing Bearer tokenNo Authorization: Bearer headerAdd the header
401Invalid JWTJWT malformed or expired, missing agent_id / owner_id, or the signature doesn’t verify against the agent’s public keyCheck the claims; if the key doesn’t match the registered public key, re-register or rotate
401Unknown agentagent_id claim doesn’t match any rowConfirm the agent exists in the dashboard
402plan_inactiveA paid plan is past_due or unpaid (Free is never inactive)Update billing
402spend_cap_reachedThe org’s opt-in spend cap is reachedRaise or disable the cap in Billing
403agent_halted (with reason: agent_suspended / agent_revoked / circuit_breaker_open and a halt block)The agent itself can’t actStop and tell the user; the owner must reactivate the agent or reset the breaker
403Permission denied (with reason, e.g. scope_not_granted, no_policy_assigned, denied_by_guardrail)The call isn’t granted, or a policy or guardrail blocks itGrant the scope in Agents → permissions, or change the policy
403AuthZEN policy deniedA policy decision from the AuthZEN evaluator denied the callInspect the audit row’s metadata.authzen_reason
403Response blocked by ingress policy (code: blocked_by_doorman)The upstream response looked like a prompt-injection payload and was withheldInspect the audit row
409needs_consentMCP-server platform: this agent hasn’t been authorized on the server yet. The body carries connect_url and poll_url; the MCP tools surface it as a -32042 URL elicitationGive the human connect_url, then retry once approved
409platform_reauth_neededThe MCP connection has expiredThe owner reconnects the platform
412consent_requiredThe person in caller hasn’t connected their own accountAsk the person to connect
412platform_not_connectedNothing is connected for this platformThe owner connects it; access_request_hint says how to ask
413Request body too large to hold for approval (code: approval_body_too_large)An approval-gated call’s body exceeds the queue limit (max_bytes)Send a smaller body
429rate_limitedThe plan’s per-minute limit for the org is exceeded (Retry-After header)Back off
502Upstream request failed (with detail: "Response could not be safely returned")PII redaction failed closed and the org has the beta flag onSee troubleshooting: PII fail-closed 502
502Upstream request failed (generic detail)The upstream call threw (network error or timeout). Not retried; no audit_id or receipt in the bodyCheck the upstream platform status; quote correlation_id
502Upstream authentication failed (code: reauth_failed)The platform rejected the stored credential and a token refresh didn’t fix itThe owner re-authorizes the platform
502Platform misconfiguredA required connection field is missingReconnect the platform with the field filled in

Audit

Every governed call writes one row to audit_log with:

  • action = the requested scope
  • result = allowed / denied / pending_approval / error
  • request_id = the response’s correlation_id
  • metadata.response_status from the upstream
  • pii_* columns if PII redaction ran
  • Forensic columns (ip_address, user_agent, geo_country, etc.) if your org has forensic capture enabled
Last updated on