Retry API v1
Turn an outbound HTTPS request into a durable job with retry scheduling, recovery, status polling, attempt history, and an optional signed terminal callback.
Updated September 8, 2026
Overview
Retry schedules attempt one immediately, records execution metadata, and retries only configured temporary failures. It does not persist or return upstream response bodies. Status responses expose the request method and destination hostname, not request secrets.
| Base URL | https://retry.exende.dev |
|---|---|
| Create | POST /v1/retries |
| Paid job price | $0.02 USDC through x402 v2 |
| Paid capability | Up to 8 attempts and a 24-hour lifetime |
| API-key path | Account-scoped creation with up to 20 attempts |
x402 quickstart
Send an unauthenticated creation request. The first response is HTTP 402 with aPAYMENT-REQUIRED header that advertises x402 v2, exact settlement, 20,000 atomic USDC units, and Baseeip155:8453. Use the returned requirements rather than hardcoding the recipient.
bash
Initial unpaid request
curl -i -X POST \
https://retry.exende.dev/v1/retries \
-H "Content-Type: application/json" \
--data '{
"request": {
"method": "GET",
"url": "https://httpbin.org/status/204"
},
"retry": {
"max_attempts": 8,
"strategy": "exponential",
"initial_delay_seconds": 5,
"max_delay_seconds": 300
}
}'json
HTTP 201 after settlement
{
"id": "retry_0123456789abcdef0123456789abcdef",
"status": "pending",
"attempts": 0,
"max_attempts": 8,
"created_at": "2026-08-24T12:00:00.000Z",
"next_attempt_at": "2026-08-24T12:00:00.000Z",
"status_url": "https://retry.exende.dev/v1/retries/retry_0123456789abcdef0123456789abcdef",
"read_token": "ex_retry_read_..."
}read_token securely. It can read only that paid job's status, attempts, and callback status. A paid response also includesPAYMENT-RESPONSE andCache-Control: private, no-store.Create a Retry job
POST https://retry.exende.dev/v1/retries
| Field | Required | Behavior |
|---|---|---|
| request.method | Yes | GET, POST, PUT, PATCH, or DELETE. |
| request.url | Yes | Public HTTPS URL, at most 2048 bytes. |
| request.headers | No | String values; blocked names and size limits apply. |
| request.body | No | JSON-compatible value or string, up to 256 KiB; forbidden for GET. |
| retry.max_attempts | No | Default 8; maximum 8 for x402 and 20 for API keys. |
| retry.strategy | No | fixed or exponential; default exponential. |
| retry.initial_delay_seconds | No | 2–3600; default 5. |
| retry.max_delay_seconds | No | 2–3600; default 300 and not below initial delay. |
| retry_status_codes | No | Exact retryable HTTP codes; at most 100. |
| success_codes | No | Exact success set; omitted means all 2xx. |
| callback_url | No | Public HTTPS terminal callback URL. |
| callback_secret | With callback | Client-generated HMAC secret, 32–512 characters. |
If the outbound body is JSON, set Content-Type inside request.headers. Exende serializes the value but does not infer the upstream content type. success_codes and retry_status_codes cannot overlap.
bash
API-key creation with idempotency and callback
curl --fail-with-body -sS \
-X POST 'https://retry.exende.dev/v1/retries' \
-H "Authorization: Bearer $EXENDE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: refresh-customer-42-v1' \
--data '{
"request": {
"method": "POST",
"url": "https://api.example.com/customers/42/refresh",
"headers": {"Content-Type":"application/json"},
"body": {"source":"agent"}
},
"retry": {
"max_attempts": 5,
"strategy": "exponential",
"initial_delay_seconds": 5,
"max_delay_seconds": 120
},
"retry_status_codes": [408,425,429,500,502,503,504],
"callback_url": "https://agent.example.com/events/exende",
"callback_secret": "client-generated-secret-at-least-32-characters"
}'Job lifecycle and status
GET /v1/retries/{id}
Authenticate with the same account API key or the matching paid-job read token.
bash
Poll job status
curl --fail-with-body -sS \
'https://retry.exende.dev/v1/retries/retry_0123456789abcdef0123456789abcdef' \
-H "Authorization: Bearer ex_retry_read_..."json
Retrying job
{
"id": "retry_0123456789abcdef0123456789abcdef",
"status": "retrying",
"request": {"method":"POST","destination_host":"api.example.com"},
"attempts": 1,
"max_attempts": 5,
"generation": 1,
"retry": {
"strategy": "exponential",
"initial_delay_seconds": 5,
"max_delay_seconds": 120
},
"last_status_code": 503,
"last_error_code": null,
"created_at": "2026-08-24T12:00:00.000Z",
"updated_at": "2026-08-24T12:00:01.000Z",
"next_attempt_at": "2026-08-24T12:00:06.000Z",
"completed_at": null,
"expires_at": "2026-08-25T12:00:00.000Z",
"attempts_url": "https://retry.exende.dev/v1/retries/retry_0123456789abcdef0123456789abcdef/attempts"
}| Status | Meaning |
|---|---|
| pending | Created and awaiting its first execution claim. |
| running | An outbound attempt currently owns the execution lease. |
| retrying | A retryable failure was recorded and another attempt is scheduled. |
| succeeded | A configured success status was received. |
| failed | A terminal result occurred or attempts were exhausted. |
| cancelled | An API-key client cancelled the job. |
| expired | The 24-hour lifetime ended before completion. |
generation is monotonic concurrency metadata. Clients should make workflow decisions from status and timestamps rather than generation.
Attempts and history
GET /v1/retries/{id}/attempts
Attempt outcomes are success, http_failure, timeout, network_error, blocked_redirect, or cancelled. Entries include timing, status/error metadata, and the scheduled delay without persisting upstream response bodies.
json
Attempt history response
{
"retry_id": "retry_0123456789abcdef0123456789abcdef",
"count": 2,
"attempts": [
{
"attempt_number": 1,
"outcome": "http_failure",
"status_code": 503,
"error_code": null,
"duration_ms": 241,
"retry_scheduled": true,
"retry_delay_seconds": 5,
"retry_after_seconds": null,
"created_at": "2026-08-24T12:00:01.000Z"
},
{
"attempt_number": 2,
"outcome": "success",
"status_code": 204,
"error_code": null,
"duration_ms": 198,
"retry_scheduled": false,
"retry_delay_seconds": null,
"retry_after_seconds": null,
"created_at": "2026-08-24T12:00:07.000Z"
}
]
}Optional terminal callback
Configure callback_url and callback_secret at creation. Retry schedules one independent delivery workflow for retry.succeeded, retry.failed, or retry.cancelled. Expired jobs do not emit a callback in v1.
http
Callback request headers
Content-Type: application/json
X-Exende-Event: retry.succeeded
X-Exende-Delivery: <stable-delivery-id>
X-Exende-Timestamp: <unix-seconds>
X-Exende-Signature: <hmac-sha256-signature>json
Signed callback payload
{
"event": "retry.succeeded",
"retry_id": "retry_0123456789abcdef0123456789abcdef",
"status": "succeeded",
"attempts": 2,
"max_attempts": 5,
"last_status_code": 204,
"last_error_code": null,
"completed_at": "2026-08-24T12:02:00.000Z"
}Delivery is at-least-once. Any 2xx response succeeds. Network errors, timeouts, and 408, 425, 429, 500, 502, 503, or 504 are retried up to 8 times within 24 hours. Retries keep the same X-Exende-Delivery value and use a fresh timestamp and signature. Verify HMAC against the exact raw body and deduplicate only after your business operation succeeds.
GET /v1/retries/{id}/callback
This protected endpoint returns safe delivery metadata only. It never exposes callback_url, callback_secret, raw payload, lease data, or internal generation.
Retry policies and idempotency
Retry behavior
Default retryable statuses are 408, 425, 429, 500, 502, 503, and 504. Timeouts and network errors are retryable. Attempt one runs immediately. Fixed delay starts at the configured initial delay; exponential delay doubles by attempt, is capped by max_delay_seconds, and is jittered. A valid Retry-After is a minimum delay capped by the configured maximum.
API-key idempotency
Idempotency-Key is optional, 1–255 characters, scoped to account plus key, and retained for 24 hours. An equivalent replay returns the original 201 response with Idempotency-Replayed: true; a different request returns 409 IDEMPOTENCY_KEY_CONFLICT.
x402 replay safety
Paid clients must include the x402 v2 payment-identifier extension. Reuse one identifier only for retries of the same logical paid creation. Exende binds it to the endpoint, payment requirements, and full request. This prevents duplicate Exende jobs but does not make a non-idempotent upstream operation exactly-once.
API-key-only operations
| Endpoint | Behavior |
|---|---|
| POST /v1/retries/{id}/cancel | Cancels a non-terminal account-owned job. A request already sent upstream cannot be recalled. |
| GET /v1/usage?from=&to= | Account-scoped outbound attempt usage. from is inclusive, to exclusive, and the maximum span is 90 days. |
Paid x402 jobs are covered by the fixed job price and do not appear as API-key billable attempts. A paid-job read token cannot cancel a job or read aggregate usage.
Errors, security, and limits
Clients should branch on error.code rather than message. Retry accepts public HTTPS destinations only, blocks IP literals and private/internal/Exende hosts, revalidates up to three same-origin redirects, and applies a 20-second timeout across the complete chain.
| HTTP | Error code |
|---|---|
| 400 | INVALID_REQUEST |
| 400 | INVALID_URL |
| 400 | URL_NOT_ALLOWED |
| 400 | INVALID_HEADERS |
| 400 | TOO_MANY_ATTEMPTS |
| 400 | INVALID_IDEMPOTENCY_KEY |
| 400 | INVALID_USAGE_RANGE |
| 400 | PAYMENT_IDENTIFIER_REQUIRED |
| 401 | MISSING_AUTHORIZATION |
| 401 | INVALID_AUTHORIZATION |
| 401 | INVALID_API_KEY |
| 401 | INVALID_STATUS_TOKEN |
| 402 | PAYMENT_REQUIRED |
| 402 | PAYMENT_SETTLEMENT_FAILED |
| 404 | RETRY_NOT_FOUND |
| 404 | NOT_FOUND |
| 409 | RETRY_ALREADY_COMPLETED |
| 409 | IDEMPOTENCY_KEY_CONFLICT |
| 409 | PAYMENT_IDENTIFIER_CONFLICT |
| 409 | PAYMENT_IN_PROGRESS |
| 413 | PAYLOAD_TOO_LARGE |
| 500 | INTERNAL_ERROR |
| 503 | PAYMENT_RECONCILIATION_PENDING |
json
Error response
{
"error": {
"code": "INVALID_REQUEST",
"message": "Human-readable explanation."
}
}Production limits
| API version | 1.0.0 |
|---|---|
| Paid job price | $0.02 USDC |
| Paid maximum attempts | 8 |
| API-key maximum attempts | 20 |
| Maximum job lifetime | 24 hours |
| Default policy | 8 attempts, exponential, 5s initial, 300s maximum |
| Retry delay range | 2–3600 seconds |
| Outbound timeout | 20 seconds across the redirect chain |
| Redirects | Up to 3, same origin only |
| Outbound body | 256 KiB maximum |
| Creation JSON | 320 KiB maximum |
| Network | Base Mainnet · eip155:8453 |
| Retention | Approximately 30 days for terminal jobs and attempt/callback records; approximately 90 days for usage events. |
OpenAPI and Bazaar discovery
The static OpenAPI 3.1 specification describes both creation paths and every public Retry endpoint. The paid POST resource is also currently discoverable through Coinbase x402 Bazaar with the exact $0.02 Base USDC requirement.
