Documentation Index
Fetch the complete documentation index at: https://docs.offloadapi.com/llms.txt
Use this file to discover all available pages before exploring further.
This is the shortest implementation path that matches the current code:
- create a case
- store the returned
caseId
- receive
case.completed, case.failed, or case.input_needed
- resume paused work with
POST /cases/{id}/input
- poll
GET /cases/{id} or fetch attachments when needed
The codebase does not hardcode a single public hostname. Use the Offload API host for your environment.
export OFFLOAD_BASE_URL="https://<your-offload-api-host>"
export OFFLOAD_API_KEY="your-api-key"
All public routes use the same header:
2. Create A Case
This example asks Offload to collect a signed W-9 over email and return structured output when the case completes.
curl -X POST "$OFFLOAD_BASE_URL/cases" \
-H "Content-Type: application/json" \
-H "x-api-key: $OFFLOAD_API_KEY" \
-d '{
"clientReferenceId": "vendor_2048",
"channel": "EMAIL",
"maxAttempts": 3,
"followUpDelayHours": 72,
"goal": {
"objective": "Collect a signed W-9 from the vendor",
"knowledge": {
"vendorName": "Northstar Supplies",
"reason": "AP onboarding",
"dueDate": "2026-04-10"
}
},
"counterparty": {
"address": "ap@northstar.example",
"name": "Maya Chen",
"company": "Northstar Supplies",
"timezone": "America/Los_Angeles",
"customData": {
"vendorId": "vendor_2048"
}
},
"senderPersona": {
"name": "Avery",
"role": "Accounts Payable",
"company": "Example Corp",
"tone": "friendly, concise"
},
"constraints": [
"Email only",
"Do not mention legal action"
],
"resultSchema": {
"type": "object",
"properties": {
"signedW9Received": { "type": "boolean" },
"taxId": { "type": "string" },
"receivedAt": { "type": "string" }
},
"required": ["signedW9Received"]
},
"clientWebhookUrl": "https://your-app.example/webhooks/offload",
"metadata": {
"vendorId": "vendor_2048",
"source": "ap-onboarding"
}
}'
Successful response:
{
"ok": true,
"data": {
"caseId": "4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f"
}
}
Important Notes About Creation
201 means the case was stored and queued. The first outbound email is sent asynchronously.
maxAttempts is the number of automated follow-up attempts after the initial outreach.
resultSchema only needs to be an object at API validation time. The API does not fully validate that it is a strict JSON Schema document.
- Repeating the same
POST /cases call creates another case. There is no idempotency key on this route.
3. Create A Case From TypeScript
const response = await fetch(`${process.env.OFFLOAD_BASE_URL}/cases`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.OFFLOAD_API_KEY!,
},
body: JSON.stringify({
channel: "EMAIL",
goal: {
objective: "Collect a signed W-9 from the vendor",
},
counterparty: {
address: "ap@northstar.example",
name: "Maya Chen",
},
senderPersona: {
name: "Avery",
},
clientWebhookUrl: "https://your-app.example/webhooks/offload",
}),
});
const body = await response.json();
if (!response.ok || !body.ok) {
throw new Error(body.error?.message ?? "Failed to create case");
}
const caseId: string = body.data.caseId;
4. Handle Webhook Events
If you set clientWebhookUrl, Offload sends a JSON POST when the case:
- completes
- fails
- pauses for human input
Example handler:
type OffloadWebhookEvent =
| {
event_type: "case.completed";
event_id: string;
timestamp: number;
data: {
caseId: string;
resultStatus?: string;
result?: Record<string, unknown>;
attachments: Array<{ attachmentId: string }>;
};
}
| {
event_type: "case.failed";
event_id: string;
timestamp: number;
data: {
caseId: string;
resultStatus?: string;
failureReason?: string | null;
attachments: Array<{ attachmentId: string }>;
};
}
| {
event_type: "case.input_needed";
event_id: string;
timestamp: number;
data: {
caseId: string;
inputRequest: string | null;
inputRequestId: string | null;
inputRequestStatus: string | null;
attachments: Array<{ attachmentId: string }>;
};
};
export async function POST(request: Request) {
const event = (await request.json()) as OffloadWebhookEvent;
switch (event.event_type) {
case "case.completed":
// Persist event_id for dedupe, then consume event.data.result
break;
case "case.failed":
// Persist failure details and decide whether your app should retry manually
break;
case "case.input_needed":
// Store inputRequestId and surface event.data.inputRequest to an operator
break;
}
return new Response(null, { status: 204 });
}
The current implementation sends webhooks with content-type: application/json, but it does not sign them and it does not retry failed deliveries. Use HTTPS, dedupe on event_id, and consider periodic polling reconciliation for important cases.
5. Resume A Paused Case
When Offload needs a human decision, it sends case.input_needed and sets the case to INPUT_NEEDED.
Resume the case with the exact inputRequestId from the webhook:
curl -X POST "$OFFLOAD_BASE_URL/cases/4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f/input" \
-H "Content-Type: application/json" \
-H "x-api-key: $OFFLOAD_API_KEY" \
-d '{
"inputRequestId": "c8c6a7ca-a913-4d13-8cd8-5887efb0531c",
"providedContext": "Yes, a signed PDF is acceptable. Ask them to send it by Friday."
}'
Accepted response:
{
"ok": true,
"data": {
"caseId": "4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f",
"inputRequestId": "c8c6a7ca-a913-4d13-8cd8-5887efb0531c",
"accepted": true
}
}
202 means the input was queued, not that the case has already resumed.
- The handler immediately marks
inputRequestStatus as RESOLVED before the worker processes the queued event.
- In that short window,
GET /cases/{id} can still show status: "INPUT_NEEDED" together with inputRequest.status: "RESOLVED".
- There is no webhook for “input accepted” or “case resumed”. You only get the next terminal or paused event.
6. Poll A Case
Use polling when:
- you did not set
clientWebhookUrl
- you want to reconcile missed webhook deliveries
- you want to fetch the plain-text transcript on demand
curl -H "x-api-key: $OFFLOAD_API_KEY" \
"$OFFLOAD_BASE_URL/cases/4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f"
Fetch the transcript:
curl -H "x-api-key: $OFFLOAD_API_KEY" \
"$OFFLOAD_BASE_URL/cases/4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f?includeTranscript=true"
includeTranscript only enables the transcript when the query value is the literal string true after trimming and lowercasing.
7. Fetch An Attachment
Webhook events and GET /cases/{id} return attachment metadata, not a permanent file URL.
curl -H "x-api-key: $OFFLOAD_API_KEY" \
"$OFFLOAD_BASE_URL/cases/4fc5904d-0e44-4ed3-aa57-c4a47ad0ae4f/attachments/63016fee-124b-46d8-a3e5-616f187a6c41"
The response includes a fresh downloadUrl plus an expiresAt timestamp. Request the link only when you are ready to fetch the file.
Next Pages