Errors & idempotency
The error envelope, common codes, and safe retries.
The error envelope
Every API error returns one JSON shape:
{
"success": false,
"code": "api_key_scope_missing",
"message": "API key is missing required scope: tools:execute",
"details": {}
}code is machine-readable and stable — branch on it, not on message. details carries optional
structured context (e.g. rate-limit metadata).
Common codes
| Status | Code | Meaning |
|---|---|---|
| 400 | validation_error / invalid_json | Request failed schema validation. |
| 401 | invalid_registration_key (and key-invalid variants) | Missing or invalid credentials. |
| 403 | api_key_scope_missing | The key lacks the required scope. |
| 403 | mcp_origin_not_allowed | Browser origin not in the MCP allowlist. |
| 404 | execution_not_found, request_log_not_found, … | Resource doesn't exist (or isn't yours). |
| 429 | rate-limit codes | Plan rate limit exceeded — see Billing. |
| 500 | internal_error | Unexpected server error. |
Authentication uses Authorization: Bearer <key> or x-stacklink-key; registration/admin endpoints
use x-stacklink-registration-key.
Retry guidance
- Safe to retry: network failures, 429 (back off), 500.
- Don't retry blindly: 400/401/403/404 — fix the request.
- Mutations: always pass an
idempotencyKey.
Idempotency
STACKLINK_EXECUTE_TOOL and STACKLINK_MULTI_EXECUTE_TOOL (and their REST/SDK forms) accept an
idempotencyKey. Replaying the same key returns the original execution record — the provider call
does not run twice:
await session.executeTool({
toolKey: 'razorpay.payments.create_refund',
input: { payment_id, amount },
idempotencyKey: `refund-${orderId}`,
});For agents, this is the difference between "the model retried" and "the customer was refunded twice."
Pair it with execution control: on timeout, STACKLINK_WORKBENCH_EXECUTION with action: "wait" or a
replay with the same key recovers the result safely.
Webhook-side retries
Outbound event deliveries have their own retry + dead-letter behavior — see Webhooks.