07

Async Underwriting API

Submit agent transactions to Trustline for pre-execution risk assessment: POST to assess-async, poll for a decision, and handle APPROVE or DECLINE.

Async underwriting is Trustline's core decision flow. An agent developer submits a transaction before the agent executes it, receives a durable transaction id immediately, then polls for a risk decision backed by audit-grade evidence. The submission is accepted in milliseconds and the assessment runs on Trustline's worker fleet, so the calling agent never blocks on the full evaluation. The same flow serves both audiences of the platform: agent developers integrating the API submit and poll directly, while institutions running those agents inherit a recorded, reviewable decision behind every approval and decline.

This page describes the developer-facing contract: how to authenticate, how to submit, how to poll, the status machine a transaction moves through, what a decision contains, and how idempotency and errors behave. The flow is the same in sandbox and production; the API key selects the environment.

Authentication

Trustline authenticates API requests with environment-scoped bearer keys created in the developer portal at portal.t54.ai. A raw key has the shape tl_{environment}_{key_id}.{secret}, where the prefix is tl_sandbox_ for sandbox keys and tl_production_ for production keys. Present it as a bearer token:

Authorization: Bearer tl_{environment}_{key_id}.{secret}

Keys are shown in full exactly once at creation and are stored only as a salted hash, so the raw secret cannot be recovered later — copy it into your secret manager when the portal displays it. Each key carries explicit scopes granted at creation. Async transaction submission requires the underwriting:write scope; a key missing it is rejected with api_key_scope_denied (HTTP 403).

Sandbox and production share a single base URL, and the environment-scoped key selects the environment:

https://portal.t54.ai/api/v1

Sandbox is self-serve today. Production access requires both approved Know Your Business (KYB) verification and explicit production enablement by the Trustline team after a launch review — KYB approval alone does not unlock production. A production key used before both gates clear is rejected with production_kyb_not_approved.

Submit a Transaction

Submit to POST /api/v1/validation/assess-async with your environment-scoped key and an Idempotency-Key header so retries never create duplicate transactions. The body carries the payment, the agent's reasoning context, the outbound request the agent is about to make, and submission metadata.

FieldDescription
assessment_type"transaction". The async endpoint accepts transaction assessments only.
agent_idStable identifier for the agent initiating the payment.
transaction_data.transactionThe payment itself: amount, currency, recipient, and optionally chain, merchant, intent, and a transaction_id you assign.
transaction_data.audit_contextThe agent's current_task (what it is doing) and reasoning_process (why it decided to pay).
transaction_data.request_bodyThe outbound request the agent intends to send, as { "http": {"url", "method"}, "body": {...} }.
metadataSubmission context: source (the integration submitting, for example x402_secure) and environment (sandbox or production).

The audit_context fields are not decoration. Trustline's validators check whether the payment is consistent with the agent's stated task and reasoning, and that same context becomes the audit evidence attached to the decision. Submissions with thin or missing context are more likely to pause for an Agentic Challenge, because Trustline asks for the evidence it could not find in the original request.

export TRUSTLINE_API_KEY="tl_sandbox_..." # created in the portal curl -sS -X POST "https://portal.t54.ai/api/v1/validation/assess-async" \ -H "Authorization: Bearer $TRUSTLINE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "assessment_type": "transaction", "agent_id": "agent_quickstart_001", "transaction_data": { "transaction": { "transaction_id": "tx_quickstart_001", "amount": 25.0, "currency": "USD", "chain": "base", "recipient": "https://merchant.example/pay" }, "audit_context": { "current_task": "Pay the merchant invoice for completed work.", "reasoning_process": "The user authorized this purchase and the amount matches the invoice." }, "request_body": { "http": { "url": "https://merchant.example/pay", "method": "POST" }, "body": {"invoice_id": "inv_123"} } }, "metadata": { "source": "x402_secure", "environment": "sandbox" } }'

The endpoint accepts the submission immediately and returns a pending transaction (underwriting_async_submission.v1) with the durable transaction id and a poll URL:

{ "schema_version": "underwriting_async_submission.v1", "status": "pending", "trustline_transaction_id": "tl_txn_...", "underwriting_request_id": "tx_quickstart_001", "job_id": "uw_job_...", "idempotency_key": "...", "poll_url": "/api/v1/underwriting/transactions/tl_txn_...", "retry_after_seconds": 3 }

Developer API keys work only with the async endpoints. The synchronous POST /api/v1/validation/assess endpoint rejects them with HTTP 400 and error code developer_api_key_requires_async_endpoint, pointing you back to the async flow. If you integrate through an agent-payment protocol rather than calling Trustline directly, two protocol variants normalize their containers into this same submit-and-poll contract: POST /api/v1/validation/assess-ap2-async accepts AP2 (Agent Payments Protocol) payment containers, and POST /api/v1/validation/assess-verifiable-intent-async accepts Verifiable Intent containers. Both return the same submission and poll response shapes described here.

Poll for the Decision

Poll GET /api/v1/underwriting/transactions/{trustline_transaction_id} until the transaction reaches a terminal status. No Authorization header is required to poll — the unguessable transaction id is the lookup handle, and the response is filtered server-side to public-safe fields. Wait retry_after_seconds between calls; it is 3 while the transaction is in flight and 0 once it is terminal.

curl -sS "https://portal.t54.ai/api/v1/underwriting/transactions/tl_txn_..."

When the assessment finishes, the poll response (underwriting_transaction_status.v1) carries the decision and a public-safe summary of the evidence behind it:

{ "schema_version": "underwriting_transaction_status.v1", "trustline_transaction_id": "tl_txn_...", "status": "completed", "decision": "APPROVE", "risk_level": "low", "confidence": 0.93, "reason_brief": "Transaction approved.", "reasons": [], "warnings": [], "retry_after_seconds": 0 }

While a transaction is paused on an Agentic Challenge, the poll response includes a challenge object describing what Trustline needs; the agent answers it through the challenge-response endpoint and polling continues. See Agentic Challenge for that flow. Polling is the completion signal today: when a challenge opens, Trustline also pushes the signed underwriting.transaction.requires_information webhook so you can react without waiting for the next poll, but completion and failure are not yet webhook-delivered. See Webhooks and Monitoring for what does and does not deliver.

Transaction Statuses

A transaction moves through a defined status machine. The statuses fall into three groups: in flight (keep polling), paused for a challenge (the agent must respond), and terminal (stop polling). Only completed carries a final decision payload; failed, expired, and canceled are terminal without one.

GroupStatusMeaningTerminal
In flightacceptedSubmission persisted; job not yet queued.No
In flightqueuedWaiting for an underwriting worker.No
In flightrunningA worker has claimed the job; underwriting in progress.No
In flightrunning_validatorsIndependent validators are assessing the transaction.No
In flightfinalizingConsensus and audit finalization in progress.No
Paused for challengerequires_informationAn Agentic Challenge is open; Trustline is waiting for the agent's answer.No
Paused for challengeinformation_submittedChallenge answer received; reassessment queued.No
Paused for challengereassessingReassessment with the new information is running.No
TerminalcompletedFinal decision available in final_result.Yes
TerminalfailedUnderwriting could not complete.Yes
TerminalexpiredThe transaction or its open challenge passed its deadline.Yes
TerminalcanceledThe transaction was canceled.Yes

What a Decision Contains

When status reaches completed, the poll response carries the decision and the public-safe references into the evidence behind it. The full developer-safe payload is in final_result.

FieldDescription
decisionAPPROVE or DECLINE only. Outcomes routed to manual review do not surface a separate decision value — they take the conservative DECLINE and explain the review posture through reason_brief.
risk_levellow, medium, high, or critical.
confidenceValidator consensus confidence in the outcome, from 0.0 to 1.0.
reason_briefA short public-safe statement of why the decision was reached, including when a transaction was held for manual review.
reasonsReason codes from a fixed, reviewable allowlist — for example EVIDENCE_INSUFFICIENT, AMOUNT_EXCEEDS_LIMIT, PROMPT_INJECTION_RISK.
warningsNon-blocking observations that did not change the decision.
audit_metadataPublic-safe references into the hash-chained audit trail behind the decision.

The decision field is intentionally binary. Trustline never returns a REVIEW or RECORD decision on this endpoint; those product postures are expressed through reason_brief and the surrounding product, not through a third decision value. The broader risk posture model — allow, review, deny, record — is described in the Trustline Overview, and how those postures map onto a single transaction decision is covered in Underwriting.

Poll responses are public-safe by contract. They expose decision state, risk level, confidence, reason codes, warnings, and public audit metadata, but never raw validator prompts, raw evidence payloads, finalizer internals, private policy material, restricted artifact URIs, or another tenant's data. See Compliance and Audit for verifying the evidence behind a decision.

Idempotency and Errors

Send an Idempotency-Key header with every submission. Replaying the same key with the same payload returns the existing transaction instead of creating a duplicate; replaying the same key with a different payload is rejected with HTTP 400 and an explicit mismatch message. This makes network retries safe without risk of double-submitting a payment for assessment.

Authentication and authorization errors return a detail object with a stable, machine-readable code in snake_case and a human-readable message. The common codes on this flow are:

CodeHTTPMeaning
invalid_api_key401Key is malformed, unknown, or fails verification.
api_key_expired401Key is past its expires_at or marked expired.
api_key_not_active401Key is revoked or otherwise not usable.
api_key_environment_prefix_mismatch401Key prefix does not match its bound environment.
api_key_scope_denied403Key lacks a scope the request requires, such as underwriting:write.
production_kyb_not_approved403Production key used before KYB approval and production enablement.
developer_api_key_requires_async_endpoint400Developer key used on the synchronous /assess endpoint; use the async endpoints.

To go from a first sandbox transaction to a production integration, start with Getting Started and manage keys, scopes, and webhook endpoints from the Developer Portal.