Asynchronous Operations
Track queued work through one provider-neutral lifecycle
Some mutations queue work instead of holding the HTTP request open. An endpoint that uses this contract returns the same operation envelope regardless of the worker or execution provider:
{
"operationId": "750c9ce8-81ed-41b9-b04e-7f9330c583ed",
"type": "team-duplicate",
"status": "queued",
"resource": {
"type": "team",
"id": "27cb2c17-ce6a-4072-838a-4835573cdf54",
"role": "source"
}
}Persist operationId. It is the stable public identifier for subsequent status
reads. Internal effect records and execution-provider identifiers are not part
of the API contract.
Read Status
GET /operations/{id}Replace {id} with the operationId returned by the initiating endpoint:
curl \
-H "x-api-key: $VRPLATFORM_API_KEY" \
"https://api.vrplatform.app/operations/750c9ce8-81ed-41b9-b04e-7f9330c583ed"The response contains the public operation type and lifecycle timestamps:
{
"id": "750c9ce8-81ed-41b9-b04e-7f9330c583ed",
"type": "vri-statement-import",
"status": "running",
"resource": {
"type": "team",
"id": "27cb2c17-ce6a-4072-838a-4835573cdf54",
"role": "target"
},
"resources": [
{
"type": "team",
"id": "27cb2c17-ce6a-4072-838a-4835573cdf54",
"role": "target"
}
],
"failure": null,
"createdAt": "2026-08-15T13:45:10.123Z",
"startedAt": "2026-08-15T13:45:12.456Z",
"finishedAt": null
}type is a stable public category, independent of the worker implementation.
resource is the primary associated domain resource. resources includes all
visible source, target, and result associations. resource is null and
resources is empty when an operation has no public domain resource. Top-level
CSV imports use that shape because uploads and previews are workflow records,
not domain resources.
When a completed operation has a file result, that resource includes a stable
authenticated url such as /files/{id}/download. The URL is absent until the
operation is completed and is not an expiring R2 reference.
| Status | Meaning |
|---|---|
queued | The operation is durably recorded and waiting for execution. |
running | A worker is executing the operation. |
completed | Every required source completed. |
failed | Every required source settled and at least one failed. |
createdAt is always present. startedAt is the first available execution or
subtask timestamp and can be null when the underlying operation does not retain
a separate start instant. finishedAt is present only after the operation is
completed or failed.
failure is null unless the operation has a safe, actionable public failure.
It never contains raw provider errors, payloads, stack traces, or worker data.
Failure codes and messages are fixed by the API:
failure.code | failure.message |
|---|---|
operationFailed | Operation failed |
csvPreviewFailed and CSV source failure codes | CSV preview failed |
csvImportFailed | CSV import failed |
csvImportRecordFailed | CSV import has record failures |
Treat an unknown failure code as a generic terminal failure. Do not display or parse internal worker errors.
Poll until the operation reaches completed or failed. Start with a short
interval and back off while the status remains non-terminal. The public API
currently provides status reads only, not retry or cancellation controls.
List Operations
GET /operationsUse status, type, createdAtFrom, and createdAtTo to filter requester-owned
history. Use resourceType and resourceId together to find operations for a
team, connection, listing, or another associated resource. The response includes
cursor-paginated items, the retained match total, and counts for all four statuses.
Resource list endpoints can embed the same summary so a refreshed UI can show pending or failed work without retaining operation IDs or making one request per row.
Initiating Endpoints
Every public endpoint that creates an operation is linked below. Conditional routes return an operation only in the cases stated.
POST /flows/{id}/connections/{connectionId}/run creates a flow-run
operation but remains excluded from the public API reference. The internal team
deletion route also creates team-delete; internal routes remain documented in
the engineering RFC instead of the public API reference.
Generated exports
Every supported generated CSV, PDF, and ZIP path exposes two delivery modes:
GETtakes its selector in query parameters, waits for admitted work, and returns{ url, expiresAt }.POSTtakes the same selector fields in its JSON body, creates a durableexportoperation, and returns the standard operation acknowledgement.
GET admission limits work to the one-minute HTTP request budget. A successful
GET does not create a public operation, including when it waits for Trigger.
GET may wait for a Trigger task. GET /statements/pdf is the only generated
artifact route that can render directly in the public request Worker. Both
methods return metadata to the client, never the artifact bytes.
Choose a client flow
Use GET first when the caller can keep the request open while the artifact is generated:
- Send GET with the selector in query parameters.
- On
200, download fromurlbeforeexpiresAt. - On
422 EXPORT_REQUIRES_POST, send POST to the same path with the same selector fields in the JSON body. - Persist the returned
operationIdand pollGET /operations/{id}. - On
completed, use the authenticatedurlon the result file resource. Onfailed, show the safefailurewhen present. Use a generic terminal failure when the API has no safe details to expose.
Change from GET to POST only when the response code is
EXPORT_REQUIRES_POST. A timeout, an aborted request, a 5xx response, or a
different error code does not establish that the selection requires POST.
Handle those failures through the Error Contract.
Call POST first when the caller needs a durable operation, background progress, or a result that remains available after the initiating request ends. A queued POST acknowledgement confirms acceptance. It does not confirm that generation completed.
An oversized GET returns this 422 response without creating an operation:
{
"code": "EXPORT_REQUIRES_POST",
"message": "Export must be queued",
"issues": [],
"context": {
"recommendedMethod": "POST",
"reason": "selectionTooLarge"
}
}Paths with GET and POST
Every path below supports awaited GET and durable POST.
/accounts/csv/bank-records/csv/contacts/csv/listings/csv/reservations/csv/reservations/line-mappings/csv/transactions/csv/statements/csv/statements/detail/csv/statements/by-period/csv/statements/pdf/statements/pdf/batch/reports/guest-balances/csv/reports/profit-and-loss/csv/reports/balance-sheet/csv/reports/trial-balance/csv/reports/journal-entries/csv/reports/manager-statements/csv/reports/manager-statements/detail/csv/reports/manager-statements/detail/pdf/reports/manager-statements/pdf/reports/manager-statements/detail/journal-entries/csv/reports/trust-reconciliation/csv/reports/trust-reconciliation/by-listing/csv/reports/trust-reconciliation/pdf/reports/trust-reconciliation/by-listing/pdf/reports/owner-statement-summaries-per-layout/csv/reports/owner-statement-summaries-per-layout/csv/detailed/reports/owner-statement-summaries-per-layout/details/{contactId}/csv/reports/owner-statement-summaries-per-layout/details/{contactId}/pdf/reports/owner-statement-summaries-per-layout/pdf/reports/sales-tax-liability/csv
Authorization and Privacy
The status route requires operations:read. Existing team and Partner
permission bundles grant that scope where operation polling is supported.
An operation belongs to the tenant that owns its public operation record. A
Partner migration operation belongs to the requesting Partner. A provider
payment recovery operation belongs to the team that owns the payout. Unknown
identifiers, unsupported internal action or sync types, malformed recovery
records, and identifiers owned by another tenant return 404.
The response never exposes the request payload, raw error details, effect
records, or the execution provider's run identifier. Failed operations can
expose only a stable code and static safe message. Internal operations without
a stable public type are not readable through this endpoint.
Poll Through MCP
Use vrt_api_read:
{
"path": "/operations/750c9ce8-81ed-41b9-b04e-7f9330c583ed"
}For API-key authentication, MCP preserves the credential tenant and does not
send x-team-id for this read. OAuth users supply the team that initiated the
operation through teamId or the MCP connection's team_id.
