Appearance
Handle errors and retries
Check three things on every call: the HTTP status, GraphQL errors, and the operation's success field. A 200 response can contain a GraphQL error or an unsuccessful operation.
Read the response
| Layer | Where to look | Examples |
|---|---|---|
| HTTP | Status and response body | 401 authentication failure, 429 endpoint throttle, 503 API unavailable |
| GraphQL | errors[].message and errors[].extensions.code | Syntax, schema validation, authorization, or processor errors |
| Operation | data.<operation>.success and its error fields | Provisioning validation, funding preflight, or webhook persistence failure |
Operation envelopes vary. Provisioning and patch responses use errorCode and errorMessage. Application responses use errorCode and error. Many card operations use success and error; processor failures can also raise a top-level GraphQL error. Record queries return rows directly.
An HTTP authentication failure can look like:
json
{ "error": "Unauthorized", "description": "Invalid signature" }A GraphQL error can look like:
json
{
"data": null,
"errors": [{ "message": "Not Authorized", "extensions": { "code": "AUTHENTICATION_ERROR" } }]
}An operation validation failure can look like:
json
{
"data": {
"createPartnerCardUser": {
"success": false,
"cardUserId": null,
"errorCode": "VALIDATION_ERROR",
"errorMessage": "Invalid input: addressCity is required."
}
}
}Ownership and scope failures return the GraphQL message Not Authorized with extensions.code AUTHENTICATION_ERROR, not HTTP 403. Some webhook and funding-status operations instead return success: false with errorCode: "FORBIDDEN" in the response payload. Depending on the operation, an inaccessible resource can also produce NOT_FOUND or an empty read result.
Fix authentication failures
A 401 is not a reason to repeat the same signed request. Check the API token's environment, expiry and revocation state, your clock, and the bytes being signed.
| Description | Check |
|---|---|
Invalid API key | Correct token and environment |
Invalid signature | Correct secret; sign the exact sent body |
Timestamp outside allowed window | Milliseconds and clock within five minutes |
Replay detected | Generate a fresh timestamp and signature for a new attempt |
If the API secret was rotated, replace it immediately; API rotation has no grace period.
Retry without duplicating work
Reads can be retried with backoff after transient failures. For mutations, a timeout or lost response can mean the side effect completed. Check the existing resource, job, or transaction before repeating the mutation.
| Operation | Recovery after an uncertain response |
|---|---|
createPartnerCardUser | Repeat the same customer organization and email; inspect reused |
createPartnerCustomerOrganization | Reconcile the customer organization with Agio; name is not an idempotency key |
createCardApplicationForPartnerUser | Query the application by applicant_id |
createCard | Query the application's cards before another issuance request |
multiSendFromWallet | Reuse the saved idempotency key and immutable batch, then poll its job |
cardFundSubClientFromTreasury | Poll a known funding job; if no ID was received, reconcile before another funding request |
cardWithdrawForPartner | Reconcile collateral history before repeating the operation |
subscribePartnerWebhook | Query subscriptions by URL; a duplicate active URL returns a result code |
Generate a fresh timestamp and HMAC signature for each retry. This request signature prevents replay; it does not make the business operation idempotent.
For funding and multisend, success: true at enqueue is not settlement. A job in submitted can have an uncertain on-chain outcome. Keep the same job and reconcile it; never replace it merely because confirmation is taking longer than expected.
Throttling and availability
Each operation has a per-minute limit per partner organization, and PIN and reveal calls also have one per card. A call over a limit returns the GraphQL error code RATE_LIMITED with extensions.retryAfterSeconds; wait that long before the next call. A refused call does not count against the limit. On HTTP 429 or CARD_RATE_LIMITED, retry with exponential backoff and jitter; do not retry all failed requests simultaneously. CARD_SIGNING_IN_FLIGHT returns retryAfterSec; wait that long before retrying.
Multisend also enforces a per-partner enqueue limit, defaulting to five requests per ten minutes. It returns RATE_LIMITED with retryAfterSec in its operation result.
HTTP 503 means the API is unavailable. Back off before a new request. For mutations, reconcile state first whenever execution may have begun.
Onboarding and card errors
These codes can appear in GraphQL errors or in the operation-specific result described above. Branch on the code at the layer that returned it; do not match free-form message text.
| Code | Action |
|---|---|
GRAPHQL_PARSE_FAILED, GRAPHQL_VALIDATION_FAILED, BAD_USER_INPUT | Correct the operation, fields, types, or enum values |
AUTHENTICATION_ERROR (GraphQL error) | Check partner ownership and scope |
FORBIDDEN (in the operation result) | Check partner ownership and supported operation |
VALIDATION_ERROR, CARD_INVALID_REQUEST | Correct the named input fields |
INCOMPLETE_ADDRESS | Patch the cardholder's address before submitting |
ATTESTATION_REQUIRED | Collect and supply agreement acceptance |
APPLICATION_ALREADY_SUBMITTED | Stop using the pre-application profile patch |
KYC_TOKEN_EXPIRED | Obtain a fresh share token through your configured KYC flow |
KYC_WORKSPACE_NOT_AUTHORIZED, KYC_PROVIDER_NOT_SUPPORTED | Contact Agio to verify KYC integration setup |
CARD_USER_NOT_FOUND on createCard | Wait for issuer cardholder data to propagate after approval |
MAX_CARDS_REACHED | The cardholder reached the active-card limit set for your account |
CARD_LIMIT_EXCEEDED | The issuer-side limit was reached; contact Agio |
CARD_NOT_FOUND, CARD_INVALID_TYPE | Check the numeric Agio ID and card type |
CARD_UNAUTHORIZED, CARD_FORBIDDEN | Check the card state and program policy |
CARD_CONFIG_ERROR | Contact Agio to verify card-service configuration |
CARD_API_ERROR | Inspect the processor's returned detail and contact Agio if unresolved |
CARD_RATE_LIMITED | Back off before the next processor call |
RATE_LIMITED | Wait extensions.retryAfterSeconds before calling the operation again |
CARD_SERVICE_UNAVAILABLE, CARD_TIMEOUT, CARD_NETWORK_ERROR | Wait, reconcile any mutation outcome, then retry if appropriate |
MAX_CARDS_REACHED ("You have reached the maximum number of active cards allowed for your account.") means the cardholder's count of cards that are not canceled or terminated reached the limit configured for your account. Agio sets the limit per account; ask your Agio contact for yours. The error carries extensions.limit and extensions.source.
Funding and withdrawal codes are documented with those operations, and multisend codes distinguish validation from background job failures.
Webhook management errors
| Code | Action |
|---|---|
DUPLICATE_ACTIVE_SUBSCRIPTION | Inspect the existing canonical URL; unsubscribe before replacing its event set |
INVALID_URL | Correct an unparseable receiver URL |
INSERT_FAILED | Check that the URL is HTTPS and inputs are valid; retry transient persistence failures |
UPDATE_FAILED | Retry a transient subscription update failure |
ROTATION_RACE | Coordinate the concurrent secret rotation before trying again |
NOT_FOUND | Check the subscription or delivery ID and your partner scope |
SUBSCRIPTION_INACTIVE | The original subscription cannot receive a resend |