Delivery targets & retention
Choose how BatchRouter delivers results — poll, webhook, or your own S3 bucket — register and test a delivery target, and set how long results are kept.
BatchRouter gives you three ways to get results out of a completed batch, and one policy that controls how long those results stay available. This guide covers when to use each delivery mode, how to register and test a delivery target (a customer-owned S3-compatible bucket), how to re-deliver a batch whose delivery failed, and how to set your retention policy.
Delivery options
Every batch produces a results artifact once it reaches completed. How you receive it is up to you:
| Mode | How it works | Best for |
|---|---|---|
| Poll (default) | Call GET /v1/batches/{id} until terminal, then read GET /v1/batches/{id}/results (paginated) or GET /v1/batches/{id}/artifact-url (signed URL). Nothing to configure. | Scripts, cron jobs, anything that can wait and ask. |
| Webhook | BatchRouter POSTs a signed event to your URL when the batch finishes. Set per-batch (webhook: { url, secret }) or org-wide with PUT /v1/auth/account/delivery-webhook. | Event-driven pipelines that react on completion. |
| Delivery target | BatchRouter writes the results artifact directly into your own S3-compatible bucket. | Keeping outputs inside your own storage/VPC, large-volume archival, no egress through your app. |
These are not mutually exclusive — you can poll and receive a webhook, or have a delivery target and still fetch results over the API.
Polling always works and needs zero setup. Reach for webhooks when you want to be notified, and for delivery targets when you want results to land in storage you control. For webhook signing and verification, see the webhooks guide.
Delivery targets (customer buckets)
A delivery target is an S3-compatible bucket you own (AWS S3, Cloudflare R2, MinIO, or any S3-API service). When a batch with a target reaches completed, BatchRouter writes the results artifact into that bucket under the target's prefix.
Customer-bucket delivery is gated behind the CUSTOMER_BUCKET_DELIVERY capability. If it isn't enabled for your org, registering and testing a target still works, but batches won't be delivered to the bucket and redeliver returns 409. Check the API reference or contact support to enable it.
Register a target
POST /v1/delivery-targets registers a bucket. The secret_access_key is write-only — it's stored encrypted and is never returned by any endpoint. The endpoint must be a public HTTPS URL; internal and metadata hosts are rejected.
Required fields: endpoint, region, bucket, access_key_id, secret_access_key. Optional: prefix, verify_before_gc (default true), is_default (default false).
curl -X POST https://api.batchrouter.com/v1/delivery-targets \
-H "Authorization: Bearer $BATCHROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"endpoint": "https://s3.us-east-1.amazonaws.com",
"region": "us-east-1",
"bucket": "my-batch-results",
"prefix": "batchrouter/",
"access_key_id": "AKIAEXAMPLE",
"secret_access_key": "wJalrXUtnFEMI/EXAMPLEKEY",
"is_default": true
}'A 201 returns the created delivery_target (id, kind, endpoint, region, bucket, prefix, access_key_id, verify_before_gc, is_default, timestamps) — never the secret. Setting is_default: true makes this the target used for batches that don't name one explicitly.
GET /v1/delivery-targets lists your configured targets (no secrets). DELETE /v1/delivery-targets/{id} removes one.
Test connectivity
Before relying on a target, run a connectivity probe. POST /v1/delivery-targets/{id}/test performs a tiny write-then-delete against the bucket — because delivery uses PUT, a read-only credential will fail the probe, which is intentional.
curl -X POST https://api.batchrouter.com/v1/delivery-targets/$TARGET_ID/test \
-H "Authorization: Bearer $BATCHROUTER_API_KEY"The response is { "test": { "ok": <bool>, "checked": "write", "error"?: { code, message } } }. On failure, error.code classifies the problem so you can fix it precisely:
| Code | Meaning |
|---|---|
credential_error | The access key / secret pair was rejected. |
permission_denied | The credential can't write to the bucket/prefix. |
bucket_not_found | The bucket doesn't exist at this endpoint/region. |
bucket_error | The bucket exists but the write failed for another reason. |
unreachable | The endpoint couldn't be reached. |
Re-deliver a failed batch
If a batch completed but its delivery to your bucket failed (an expired credential, a transient outage), re-enqueue it with POST /v1/batches/{batchId}/redeliver.
curl -X POST https://api.batchrouter.com/v1/batches/$BATCH_ID/redeliver \
-H "Authorization: Bearer $BATCHROUTER_API_KEY"A 202 re-enqueues delivery. The call is guarded against double-delivery via the batch's delivery_status: it returns 409 if the batch is already delivered or delivering, or if customer-bucket delivery isn't enabled / the batch has no configured target.
Result retention
Retention controls how long BatchRouter keeps a batch's results before garbage-collecting the stored bytes. It's an org-wide policy you read and set via GET|PUT /v1/retention-settings. The default is the free grace_only tier.
| Tier | What it keeps | Cost |
|---|---|---|
grace_only (default) | Results for a short free grace window after completion, then GC. | Free |
extended_fixed | Results for a fixed extended_days (1–3650). | Metered storage charge |
until_deleted | Results until you delete them. | Metered storage charge |
extended_fixed and until_deleted are metered — once storage fees are enabled they incur a storage_charge against your prepaid wallet. Set never_charge: true to guarantee zero storage spend; this forces the free grace_only tier. extended_fixed requires extended_days. See the API reference for the exact charge model and the fee_acknowledged / auto_extend flags.
Read the current policy
curl https://api.batchrouter.com/v1/retention-settings \
-H "Authorization: Bearer $BATCHROUTER_API_KEY"Set the policy
retention_tier is the only required field. The example below keeps results for 90 days.
curl -X PUT https://api.batchrouter.com/v1/retention-settings \
-H "Authorization: Bearer $BATCHROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"retention_tier": "extended_fixed",
"extended_days": 90,
"fee_acknowledged": true
}'Retention governs how long results stay fetchable over the API and via signed artifact URLs. Once a batch's bytes are GC'd, a results download returns 410 (expired), distinct from a 404 for a batch that never existed. Delivering to your own bucket sidesteps this — the copy in your storage is yours to keep regardless of BatchRouter's retention tier.