Errors
Every 4xx and 5xx response from the Yieldforce B2B API uses RFC 9457 application/problem+json. The code field is a stable, snake_case string you can branch on programmatically — HTTP status codes alone are not sufficient for error handling.
Format
Every error response has Content-Type: application/problem+json. If you receive a different
content type on an error, something intercepted the request before it reached the API (a proxy,
firewall, or load balancer).
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json
X-Yieldforce-Request-Id: req_01J8Z3K2V7BMQ...
X-Yieldforce-Api-Version: 1.0.0
{
"type": "https://docs.yieldforce.io/errors/insufficient_balance",
"title": "Insufficient balance",
"status": 422,
"detail": "Wallet 0xabc...12f has 42.15 USDC available; requested 100.00",
"instance": "/v1/end_users/eu_01HX/portfolios/pf_01HY/withdrawals",
"code": "insufficient_balance",
"param": "amount",
"request_id": "req_01J8Z3K2V7BMQ...",
"doc_url": "https://docs.yieldforce.io/errors/insufficient_balance"
}Fields
Response body
Stable URL identifying the error class. Always resolves to human-readable documentation for this error.
Short human-readable label (5–8 words). Stable per code — safe to display to developers, not
to end users.
HTTP status code, mirroring the response status. Numeric.
Human-readable explanation specific to this instance, including dynamic context (amounts, IDs,
wallet addresses). Do not parse this field programmatically — it may change. Use code instead.
URI path of the request that failed.
Stable, snake_case, machine-readable identifier. Branch your error-handling logic on this field. Codes are never renamed or renumbered within a major API version.
Name of the request parameter that caused the error (Stripe convention). Present only when the
error is field-specific (e.g., "param": "amount").
Opaque request identifier. Include this in support tickets so the team can locate the request in logs.
Link to the docs page for this specific error code.
Request ID for support
Every response — success or error — includes the X-Yieldforce-Request-Id response header. Its
value matches request_id in error bodies. Always include it when opening a support ticket; it
lets the team pull the full request trace immediately.
HTTP status code conventions
| Status | When |
|---|---|
400 | Malformed request — bad JSON, missing required field, malformed parameter |
401 | Authentication failed — JWT invalid, expired, or signature mismatch |
403 | Authenticated but caller lacks permission — wrong role, wrong tenant, inactive account |
404 | Resource not found or belongs to a different tenant (no enumeration leak) |
409 | Conflict — idempotency key in-flight, duplicate resource, version conflict |
410 | Gone — cursor expired |
422 | Semantically invalid — insufficient balance, invalid amount, bad address |
429 | Rate limit exceeded |
5xx | Server error — safe to retry with exponential backoff |
Error code catalog
Authentication — 401
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
unauthenticated | 401 | No valid credentials found | Missing Authorization header or X-User-Token | Add the required auth header |
invalid_token | 401 | JWT failed signature / format validation | Malformed token, wrong signing key | Re-fetch a fresh token; check your signing key |
invalid_entra_token | 401 | Entra (M2M) token is invalid | Wrong audience, expired, bad signature | Re-fetch Entra token via client credentials grant |
invalid_user_token | 401 | Partner-signed user JWT is invalid | Wrong RSA key, bad format, expired exp | Reissue a fresh partner JWT |
Authorization — 403
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
unknown_partner | 403 | Tenant not found in the registry | Wrong Entra tenant ID in token | Verify your Azure AD tenant is registered with Yieldforce |
unknown_partner_issuer | 403 | JWKS issuer not recognized | Partner JWT issuer (iss) not in tenant config | Register your JWKS endpoint with Yieldforce |
cross_tenant_jwt | 403 | JWT belongs to a different tenant | Using another tenant's token on your endpoint | Ensure you're using your own credentials |
sub_url_mismatch | 403 | JWT sub does not match the end user in the URL path | End user ID in URL doesn't match the JWT sub | Pass the correct end user ID matching the token |
insufficient_role | 403 | Caller's role is not authorized for this action | Wrong scope or role on Entra app registration | Contact Yieldforce to check tenant role assignment |
Idempotency — 409 / 422
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
idempotency_key_required | 400 | Idempotency-Key header missing on a money-moving endpoint | Forgot the header on deposit/withdraw | Add the header with a UUID v4 value |
idempotency_key_in_progress | 409 | Same key is in-flight in an ongoing request | Concurrent duplicate request | Wait for the original request to complete, then replay |
idempotency_key_in_use_with_different_params | 422 | Same key, different request body | Reused a key for a logically different operation | Generate a new key for the new operation |
Pagination — 400 / 410
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
cursor_invalid | 400 | Cursor is malformed or tampered | Manually constructing or modifying cursor strings | Use the next_cursor / prev_cursor values exactly as returned |
cursor_expired | 410 | Cursor TTL (7 days) has elapsed | Storing a cursor and using it weeks later | Re-fetch the first page to get a fresh cursor |
End user / Portfolio — 400 / 404
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
end_user_not_found | 404 | End user does not exist or belongs to another tenant | Wrong end_user_id | Verify the ID or create the end user first |
end_user_lookup_failed | 500 | Internal error resolving end user | Backend / DB error | Retry; contact support if persistent |
end_user_deleted | 400 | End user has been deleted | Attempting to act on a deleted user | Do not attempt further operations on deleted users |
portfolio_not_found | 404 | Portfolio not found | Wrong portfolio ID or wrong end user | Verify both IDs |
external_id_jwt_sub_mismatch | 400 | external_id in create-user body doesn't match JWT sub | Passing mismatched IDs | Ensure external_id matches the sub in the user JWT |
Tenant / Wallet configuration — 400 / 500
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
tenant_privy_not_configured | 500 | Tenant wallet provisioning is not configured | Missing wallet setup in partner onboarding | Contact Yieldforce to complete tenant onboarding |
privy_provisioning_failed | 500 | Wallet provisioning failed for a new end user | Wallet service error during user creation | Retry; contact support if persistent |
On-chain — 5xx
| Code | Status | Meaning | Common trigger | Fix |
|---|---|---|---|---|
chain_read_failed | 500 | Failed to read balance or state from the chain | RPC node error or network outage | Retry with backoff; check chain status |