Skip to main content

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:
  1. create a case
  2. store the returned caseId
  3. receive case.completed, case.failed, or case.input_needed
  4. resume paused work with POST /cases/{id}/input
  5. poll GET /cases/{id} or fetch attachments when needed

1. Configure Your API Host And Key

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:
x-api-key: your-api-key

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
  }
}

Important Notes About Input Submission

  • 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