Skip to Content
API ReferenceGet Result

Get Result

Fetch the stored result of a completed request — no webhook required.

GET /cargo/{cargo_id}/result

Convoy stores every result server-side for 30 days (configurable per request with result_ttl_seconds on /cargo/load). This endpoint returns that stored result in the exact same shape as the callback webhook payload, so your parsing code works identically whether the result arrived by webhook or by polling.

Use it to:

  • Skip webhooks entirely — submit with no callback_url (“mailbox-only”) and poll for the result when you’re ready.
  • Recover from callback_failed — if Convoy exhausted its webhook retries, the result is still here.
  • Fetch large results on demand — agents, scripts, and CLIs that have no public HTTPS endpoint.

Authentication

This endpoint requires the X-API-Key header with a valid project API key.

-H "X-API-Key: convoy_sk_your_key_here"

Project Scoping: You can only fetch results for cargo that belongs to your project. Cargo from another project returns 404 Not Found.

Path Parameters

ParameterTypeDescription
cargo_idstringThe cargo ID from /cargo/load response

Response

Same shape as the webhook callback payload:

{ "cargo_id": "cargo_abc123def456", "success": true, "response": { "id": "msg_01ABC...", "content": [{"type": "text", "text": "..."}], "usage": {"input_tokens": 25, "output_tokens": 150} }, "error_message": null, "created_at": "2024-01-15T11:45:00Z", "metadata": {"job": "nightly-42"} }
FieldDescription
cargo_idRequest identifier
successWhether processing succeeded
responseFull model response (null on failure)
error_messageError details when success is false
created_atWhen the result was produced
metadataYour opaque metadata from /cargo/load, echoed verbatim (null if none)

Status Codes

StatusMeaning
200Result available — body above (also for failed cargo, with success: false)
401Missing or invalid API key
403Project is inactive
404Cargo not found or not owned by your project
409Cargo is not finished yet — keep polling (current status in body)
410Result expired and was purged (past its retention TTL)
429Rate limit exceeded

409 — not finished yet

{ "detail": { "error": "result_not_ready", "message": "Cargo cargo_abc123def456 is not finished yet (status: processing). ...", "cargo_id": "cargo_abc123def456", "status": "processing", "status_description": "Batch is being processed by the provider" } }

410 — expired

{ "detail": { "error": "result_expired", "message": "The result for cargo cargo_abc123def456 has expired and is no longer retrievable.", "cargo_id": "cargo_abc123def456", "status": "completed" } }

Results are retained for 30 days by default (or the result_ttl_seconds you set at submission — 1 hour to 30 days). After that they are permanently purged and this endpoint returns 410 Gone. Fetch and persist results you need long-term.

Mailbox-only submission (no webhook)

callback_url is optional on /cargo/load. When omitted, no webhook is ever sent — the cargo terminates at completed or failed and you retrieve the result here:

import httpx import time API = "https://api.cnvy.ai" HEADERS = {"X-API-Key": "convoy_sk_your_key_here"} # 1. Submit WITHOUT a callback_url submit = httpx.post(f"{API}/cargo/load", headers=HEADERS, json={ "params": { "model": "claude-haiku-4-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Summarize this document..."}], }, # no callback_url — mailbox-only "result_ttl_seconds": 604800, # keep the result for 7 days (optional) }) cargo_id = submit.json()["cargo_id"] # 2. Poll for the result (409 = not done yet) while True: r = httpx.get(f"{API}/cargo/{cargo_id}/result", headers=HEADERS) if r.status_code == 200: result = r.json() break if r.status_code == 409: time.sleep(30) continue r.raise_for_status() if result["success"]: print(result["response"]["content"][0]["text"]) else: print(f"Failed: {result['error_message']}")

Polling etiquette: batch results take minutes to hours. Poll /cargo/{cargo_id}/tracking (cheap, status-only) at your chosen interval and fetch /result once the status is terminal — or just poll /result directly and treat 409 as “not yet”. Either works; tracking bodies are smaller.

Recovering from callback_failed

If your webhook endpoint was down and Convoy exhausted its retry schedule, the cargo lands in callback_failed — but the result is not lost:

curl -H "X-API-Key: convoy_sk_your_key_here" \ https://api.cnvy.ai/cargo/cargo_abc123def456/result

Returns the full result payload exactly as the webhook would have delivered it.

Examples

curl

curl -H "X-API-Key: convoy_sk_your_key_here" \ https://api.cnvy.ai/cargo/cargo_abc123def456/result

JavaScript

async function getResult(cargoId, apiKey) { const response = await fetch( `https://api.cnvy.ai/cargo/${cargoId}/result`, { headers: { "X-API-Key": apiKey } } ); if (response.status === 409) return null; // not finished yet if (response.status === 410) throw new Error("Result expired"); if (!response.ok) throw new Error(`HTTP ${response.status}`); return response.json(); }

When to use webhooks vs the mailbox

Webhook (callback_url)Mailbox (/result)
LatencyPush — arrives seconds after completionPoll — bounded by your interval
InfrastructureNeeds a public HTTPS endpointNone
Reliability5 attempts over ~40 minutes, then callback_failedResult stored up to 30 days
Best forServers, pipelines, AWS Lambda durable functionsAgents, scripts, CLIs, notebooks

You can use both: provide a callback_url for push latency and fall back to /result if the webhook is missed. Results are stored server-side either way.

Last updated on