Skip to Content
AgentFlow
AI agents

Requests

Submit and poll approval requests through the agent API.

Requests are the core of the agent integration. This page covers the request procedures. All examples assume the API at https://af-api.ivalt.com; see Connections for auth.

submitRequest

Creates an approval request in pending state and routes it to a reviewer according to your routing configuration.

curl -s https://af-api.ivalt.com/api/v1/ai-agent/submitRequest \
  -H 'x-ai-agent-key: <agent-key>' \
  -H 'content-type: application/json' \
  -d '{
    "json": {
      "externalRequestId": "REQ-1001",
      "idempotencyKey": "0f2a5f18-2f4c-4f8f-9c77-3b4f4c5f9a11",
      "title": "On-behalf-of correspondence",
      "actionType": "send_mail",
      "actionSummary": "Agent drafted mail on behalf of insured",
      "displaySnapshot": { "recipient": "vendor@example.com", "amount": 4500 },
      "contentHash": "sha256:2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
      "expiresAt": "2026-11-17T22:00:00.000Z",
      "riskLevel": "high"
    }
  }'
FieldTypeRequiredDescription
externalRequestIdstringYesYour unique ID for the request; unique per connection
idempotencyKeystringYesClient-generated key (UUID recommended); unique per connection
recipientUserIdstringNoSend the request directly to this workspace user (must be an active or invited approver); bypasses routing
titlestringYesDisplay title shown to the reviewer
actionTypestringYesAction category
actionSummarystringYesShort human-readable summary
displaySnapshotobjectYesArbitrary JSON rendered for the reviewer; title, actionType, actionSummary are merged into it
contentHashstringYesHash of the full action content; stored as the receipt's requestHash
expiresAtISO 8601 datetimeYesWhen the request expires if undecided
riskLevelenumNolow | medium | high | critical; defaults to medium

The response is the full request object (status: "pending", version: 1, id like apr_…). Store id — you need it for every subsequent call.

Default routing and recipient routing

Every workspace has a built-in default approval path that routes to the workspace's approver role, so agents can submit before any routing is configured.

Pass recipientUserId to send the request straight to a specific approver — the user ID as it appears under People & roles in the console. If the user isn't an active or invited member of the workspace, the call fails with bad_request.

Errors

ConditionError
Missing/invalid fieldsvalidation_error (400)
Missing or unknown x-ai-agent-keyunauthorized (401)
No assignee resolvable — routing unconfigured, or recipientUserId isn't an active/invited workspace userbad_request (400)
Idempotency key or externalRequestId already usedidempotency_conflict (409)

Idempotency

Uniqueness is enforced per connection on both externalRequestId and idempotencyKey. Retrying with the same idempotencyKey returns the originally created request, so submitRequest is safe to retry on timeouts — generate the key once per logical request, not once per attempt.

Edge case: if routing resolution fails after the request is accepted, submitRequest can return bad_request even though the request exists without an assignee — and an idempotent replay returns that unassigned request rather than an error. Treat a bad_request from submitRequest as possibly having persisted the request: follow up with getStatus on the same idempotencyKey/externalRequestId before resubmitting with a new key.

getStatus

Returns the request plus delivery-aware status. This is the polling entry point.

curl -s https://af-api.ivalt.com/api/v1/ai-agent/getStatus \
  -H 'x-ai-agent-key: <agent-key>' \
  -H 'content-type: application/json' \
  -d '{"json": {"requestId": "apr_1234_v1"}}'
{
  "json": {
    "request": { "id": "apr_1234_v1", "status": "pending", "version": 1, "...": "..." },
    "isFinal": false,
    "deliveryModes": ["polling", "webhook"],
    "reviewProgress": {
      "assigned": 1,
      "completed": 0,
      "requiredApprovals": 1,
      "decisions": []
    },
    "receiptId": null,
    "nextPollAfterMs": 5000
  }
}
FieldMeaning
requestThe full request object; status is one of pending, opened, decision_in_progress, verification_required, approved, rejected, expired, failed
isFinaltrue once the status is approved, rejected, or expired
deliveryModesWhich delivery modes apply to this request: polling (always), plus webhook when configured
reviewProgressReviewer progress for the request. Each request currently has a single approver
receiptIdThe proof/receipt ID once a decision is finalized, else null
nextPollAfterMsMinimum delay before polling again. Wait at least this long between calls; do not hard-code a tighter interval

getRequest

Returns the raw request object only — same shape as the submitRequest response, without the status envelope. Use it when you don't need isFinal/reviewProgress/deliveryModes.

curl -s https://af-api.ivalt.com/api/v1/ai-agent/getRequest \
  -H 'x-ai-agent-key: <agent-key>' \
  -H 'content-type: application/json' \
  -d '{"json": {"requestId": "apr_1234_v1"}}'

Polling recipe

import time, requests

def wait_for_decision(api, key, request_id):
    while True:
        r = requests.post(
            f"{api}/api/v1/ai-agent/getStatus",
            headers={"x-ai-agent-key": key},
            json={"json": {"requestId": request_id}},
        )
        status = r.json()["json"]
        if status["isFinal"]:
            return status
        time.sleep(status["nextPollAfterMs"] / 1000)

When isFinal becomes true, check request.status: only approved authorizes the protected action, and only after you verify the signed receipt — see Receipts.

On this page