# Error Handling

Complete reference for all error codes returned by the Kobaru Gateway API.

When an API request fails, Kobaru returns a structured error response with a specific error code. Use this reference to understand what went wrong and how to fix it.

---

## Error response format

All errors follow a consistent JSON structure:

```json
{
  "isValid": false,
  "invalidReason": "error_code_here",
  "error": "Human-readable error message"
}
```

| Field | Type | Description |
|-------|------|-------------|
| isValid | boolean | Always `false` for error responses |
| invalidReason | string | Machine-readable error code from this reference |
| error | string | Human-readable error description (optional) |

For settlement errors, the response uses a slightly different structure:

```json
{
  "success": false,
  "errorReason": "error_code_here",
  "error": "Human-readable error message"
}
```

---

## HTTP status codes

Kobaru maps error codes to appropriate HTTP status codes:

| HTTP Status | Category | When Used |
|-------------|----------|-----------|
| 400 Bad Request | Client Error | Invalid request format, missing fields, validation failures |
| 401 Unauthorized | Authentication | Missing or invalid API key |
| 403 Forbidden | Authorization | Valid API key but insufficient permissions |
| 404 Not Found | Resource | Requested resource does not exist |
| 409 Conflict | State | Payment busy or unresolved: `duplicate_request`, `settlement_pending`, `idempotency_conflict` |
| 422 Unprocessable Entity | Validation | Request is well-formed but semantically invalid |
| 429 Too Many Requests | Rate Limit | Rate limit exceeded (enforced by Kobaru's firewall, not the gateway) |
| 500 Internal Server Error | Server | Unexpected server error |
| 503 Service Unavailable | Server | Temporarily unavailable, retry after `Retry-After`: `claim_unavailable`, `rpc_unavailable`, `bite_unavailable` (facilitator); `duplicate_request`, `claim_unavailable`, `settlement_pending`, `server_configuration_error`, `unexpected_verify_error`, `rpc_unavailable`, `bite_unavailable` (proxy) |

### Replayed results

When `/verify` or `/settle` returns the stored result of an earlier request for the same payment, the body includes `"replayed": true` and the response has the header `Idempotent-Replayed: true`. Fresh results omit both. See [Idempotency](../concepts/idempotency.md) for how Kobaru identifies a payment and what it guarantees.

- A replayed settlement **confirms a charge that already happened**; it is not a new one. Keep an order/fulfillment record per payment and don't deliver again on a replay you already fulfilled.
- A stored success is only returned to a request carrying **the same signature** as the payment (case-insensitive). Another signature for a known payment is verified again, or answered `payment_proof_mismatch` if the payment is settled.
- Only binding outcomes are replayed: a success, a failure on chain, an expiry, a pending outcome or an attempt in progress. An attempt rejected before anything was broadcast (for example `insufficient_funds` or a simulation failure) is evaluated again on the next request.

---

### Idempotency and settlement codes (`/verify`, `/settle`)

| Code | HTTP | Retry-After | Endpoint | Meaning | What to do |
|------|------|-------------|----------|---------|------------|
| `duplicate_request` | 409 | 5 | both | The same payment is being processed right now | Retry after `Retry-After` |
| `settlement_pending` | 409 | 30 | both | A transaction was (or may have been) broadcast and its outcome isn't known yet. On `/verify`: a settle of this payment is unresolved (the verification is not re-run). `transaction` is included when known | Check `transaction` on chain. Retry after `Retry-After`: a `/settle` retry checks the chain first and returns the real outcome when it can. If the chain can't tell (EVM authorization used by an unknown transaction), it stays pending until reconciliation; contact support if it persists. Don't deliver or ask for a new payment meanwhile |
| `claim_unavailable` | 503 | 5 | both | The payment couldn't be claimed (database unavailable). Nothing was recorded or broadcast | Retry after `Retry-After` |
| `rpc_unavailable` | 503 | 5 | both | EVM (Base, SKALE): the network's RPC couldn't be reached while reading the token contract (authorization nonce or EIP-712 domain): a transport error or timeout. Nothing was broadcast; the attempt is evaluated again on the next request. `evm_contract_not_eip3009` (400) is only returned when the contract itself answered (a revert or empty return data) | Retry the same payload after `Retry-After` |
| `bite_unavailable` | 503 | 10 | both | SKALE with BITE: the encryption service is unreachable (health check on `/verify`; encryption on `/settle` failed with a transport error, timeout or 5xx). Nothing was broadcast | Retry the same payload after `Retry-After` |
| `idempotency_conflict` | 409 | none | both | The payment already has a binding outcome with a different `payTo` or `amount`. (A different network, scheme or token is a different payment, not a conflict) | Don't retry. Use the original requirements or a new payment |
| `payment_proof_mismatch` | 400 | none | both | The payment is known, but this request's signature isn't the one it was verified, settled, or first broadcast with | Treat as invalid; use the original payload |
| `payment_already_settled` | 400 | none | `/verify` | Settled (same signature). Includes `transaction`, and `payer` when known | Treat the payment as used; check your fulfillment record |
| `payment_settlement_failed` | 400 | none | `/verify` | The settlement failed for good: reverted on chain, or expired without landing. Includes `transaction` when there was one | Don't deliver; the payer needs a new payment |
| `invalid_exact_evm_payload_signature` | 400 | none | both | EVM signature invalid, including non-canonical ones the token contract rejects (high s, v not 27/28). On `/settle` it is rejected before anything is recorded | The client must sign again |
| `invalid_exact_solana_payload_signature` | 400 | none | both | Solana: a client (non-fee-payer) signature is missing or doesn't verify. On `/settle` it is rejected before anything is recorded | The client must sign the transaction correctly |
| `invalid_exact_evm_payload_authorization_valid_before` / `blockhash_expired` | 400 | none | `/settle` | Expired before anything was broadcast: final, replayed with `replayed: true` on later requests | A new payment is needed |

A `/settle` that finds the authorization already used on chain (nonce or signature used, a front-run transaction, "already processed") is **not** reported as a failure: someone executed it, so the answer is `settlement_pending` until the outcome is known.

### Proxy codes (`access.kobaru.io`)

| `reason` | HTTP | Retry-After | Meaning | What the client should do |
|----------|------|-------------|---------|---------------------------|
| `duplicate_request` | 503 | 5 | The same payment is being processed by another request (answered before your API is called) | Retry after `Retry-After` |
| `claim_unavailable` | 503 | 5 | The payment couldn't be claimed right now. If your API already answered, the use is refunded | Retry after `Retry-After` |
| `server_configuration_error` | 503 | 30 | The gateway can't process this payment network right now (server-side). Nothing was broadcast; if your API already answered, the use is refunded | Retry after `Retry-After`; don't pay again |
| `unexpected_verify_error` | 503 | 5 | Verification failed unexpectedly on the server (answered before your API is called) | Retry after `Retry-After`; don't pay again |
| `rpc_unavailable` | 503 | 5 | The network's RPC couldn't be reached while verifying the payment (answered before your API is called). Nothing was broadcast | Retry after `Retry-After`; don't pay again |
| `bite_unavailable` | 503 | 10 | SKALE BITE encryption is unreachable (body error `Payment encryption temporarily unavailable`). Nothing was broadcast; if your API already answered, the use is refunded | Retry after `Retry-After`; don't pay again |
| `settlement_pending` | 503 | 30 | The settlement has an unknown outcome (`transaction` when known). Before your API is called, the proxy first tries to resolve it on chain | Retry after `Retry-After`; don't pay again. When it resolves, the retry is served without costing a new use |
| `payment_settlement_failed` | 402 | none | The settlement failed for good (on chain, or expired). Other settle rejections keep their own reason (e.g. `payment_proof_mismatch`, a signature code) | Pay again with a new payment |
| `payment_expired` | 402 | none | The payment is settled but its quota has lapsed (or was never created) | Pay again |
| `payment_already_used` | 402 | none | A single-use payment was already used | Pay again |
| `usage_exhausted` | 402 | none | The quota is used up | Pay again |
| `payment_bound_to_other_route` | 402 | none | The payment's quota belongs to the first service route that used it | Use it on that route, or pay for this one |
| `payment_proof_mismatch` | 402 | none | The signature isn't the one the payment was made with | Use the original payload |

A 503 from the proxy is never a payment failure: retry after `Retry-After`, never pay again for it.

---

## General error codes

These errors apply across all payment networks and endpoints.

### Request validation errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `invalid_x402_version` | 400 | The `x402Version` field is not `2` | Ensure your request includes `"x402Version": 2` in the payload |
| `invalid_payload` | 400 | Missing or malformed `paymentPayload` | Verify the payment payload structure matches the x402 v2 specification |
| `invalid_payment_requirements` | 400 | Missing or invalid `paymentRequirements` | Include all required fields: `scheme`, `network`, `amount`, `asset`, `payTo` |
| `invalid_network` | 400 | The specified network is invalid or unsupported | Use a valid CAIP-2 network identifier (e.g., `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`) |
| `unsupported_scheme` | 400 | The payment scheme is not supported | Kobaru currently supports the `exact` scheme |

### Capability errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `capability_not_enabled` | 400 | The requested network/scheme/asset combination is not enabled for this client | Enable the capability in your Kobaru Console or contact support |

### Concurrency errors

The idempotency and settlement codes (`duplicate_request`, `settlement_pending`, `claim_unavailable`, `idempotency_conflict`, `payment_proof_mismatch`, `payment_already_settled`, `payment_settlement_failed`, and the signature codes) are listed with their status, `Retry-After` and retry advice in [Idempotency and settlement codes](#idempotency-and-settlement-codes-verify-settle). `concurrent_capture_in_progress` is no longer returned.

### Server errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `unexpected_verify_error` | 500 | An unexpected error occurred during payment verification | Retry the request. If the error persists, contact support |
| `unexpected_settle_error` | 500 | An unexpected error occurred during payment settlement | Check transaction status on-chain before retrying. Contact support if the error persists |
| `server_configuration_error` | 500 | The server is missing required configuration. When the network's driver can't be set up, nothing is recorded (a payment with a stored result still gets it: a replay, `duplicate_request` or `settlement_pending`). When a driver returns it after the claim (for example Solana without a fee payer: verify fails closed), a `rejected` REJECTED/FAILED row is written and the payment stays re-evaluable | Contact Kobaru support. This indicates a server-side configuration issue; the same payload can be retried once it's fixed. The proxy answers 503 (`Retry-After: 30`), never 402. Also returned when the SKALE BITE encryption service answers `/settle` with something unusable (4xx, non-JSON body, invalid encryption); an unreachable service is `bite_unavailable` |
| `rpc_unavailable` | 503 | EVM: the network's RPC couldn't be reached while reading the token contract. Nothing was broadcast | Retry after `Retry-After` (5). See [Idempotency and settlement codes](#idempotency-and-settlement-codes-verify-settle) |
| `bite_unavailable` | 503 | SKALE: the BITE encryption service is unreachable. Nothing was broadcast | Retry after `Retry-After` (10). See [Idempotency and settlement codes](#idempotency-and-settlement-codes-verify-settle) |

---

## Solana-specific error codes

These errors occur when processing payments on the Solana network.

### Amount and recipient errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `invalid_exact_solana_payload_authorization_value` | 400 | The transfer amount does not match the required amount | Ensure the transaction transfers exactly the amount specified in `paymentRequirements.amount` |
| `invalid_exact_solana_payload_recipient_mismatch` | 400 | The destination address does not match the required `payTo` address | Verify the transfer is sent to the correct Associated Token Account (ATA) for the `payTo` wallet |
| `invalid_asset_mismatch` | 400 | The token mint does not match the required asset | Use the correct SPL token mint address specified in `paymentRequirements.asset` |

### Transaction format errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `invalid_transaction_format` | 400 | The transaction cannot be decoded | Ensure the transaction is a valid base64-encoded Solana transaction |
| `transaction_too_large` | 400 | The transaction exceeds the maximum allowed size | Reduce the transaction size by removing unnecessary instructions |
| `malformed_instruction` | 400 | An instruction in the transaction cannot be parsed | Verify all instructions use correct program IDs and data formats |

### Fee payer errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `invalid_fee_payer` | 400 | The transaction fee payer is not the Kobaru facilitator | Set the facilitator's public key as the fee payer. The facilitator covers transaction fees |

### Compute budget errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `missing_compute_budget_limit` | 400 | Transaction is missing the `SetComputeUnitLimit` instruction | Add a `SetComputeUnitLimit` instruction as the first instruction |
| `missing_compute_budget_price` | 400 | Transaction is missing the `SetComputeUnitPrice` instruction | Add a `SetComputeUnitPrice` instruction as the second instruction |
| `invalid_compute_unit_limit` | 400 | The compute unit limit is invalid or exceeds allowed maximum | Set a reasonable compute unit limit (typically 200,000-400,000 for token transfers) |
| `priority_fee_exceeds_limit` | 400 | The priority fee exceeds the maximum allowed by the facilitator | Reduce the priority fee. Maximum is configured by the facilitator (default: 100,000 lamports) |

### Instruction topology errors

Kobaru enforces a strict instruction order to prevent transaction manipulation:

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `invalid_instruction_count_topology` | 400 | The transaction has an unexpected number of instructions | Transactions must have exactly 4 instructions (or 5 if creating an ATA) |
| `invalid_instruction_topology_expected_create_ata` | 400 | Expected a CreateAssociatedTokenAccount instruction but found something else | If the recipient ATA doesn't exist, include a CreateATA instruction in position 3 |
| `invalid_instruction_topology_ata_mismatch` | 400 | The ATA being created doesn't match the expected recipient | Ensure the CreateATA instruction creates an ATA for the correct recipient and mint |
| `missing_transfer_instruction` | 400 | No SPL token transfer instruction found (e.g. another Token instruction such as `ApproveChecked` in the transfer slot) | Include a `TransferChecked` instruction as the final instruction |
| `unsupported_token_program` | 400 | The transfer is not an instruction of the standard SPL Token program (`TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA`) | Use the standard SPL Token program. Token-2022 transfers are answered with `unsupported_scheme` |

**Required instruction order:**
1. `SetComputeUnitLimit` (ComputeBudget program)
2. `SetComputeUnitPrice` (ComputeBudget program)
3. `CreateAssociatedTokenAccount` or `CreateAssociatedTokenAccountIdempotent` (optional, if recipient ATA doesn't exist; Associated Token program, System and SPL Token program accounts)
4. `TransferChecked` (SPL Token program, 4 accounts: no multisig)

Memo instructions (Memo program) may appear anywhere. Each instruction must match its program, discriminator and data length exactly: trailing bytes are rejected (`malformed_instruction`).

### Security errors

These errors prevent unauthorized use of the facilitator's resources:

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `facilitator_cannot_pay_ata_creation` | 400 | The transaction attempts to have the facilitator pay for ATA creation rent | The payer (not the facilitator) must pay for ATA creation |
| `facilitator_cannot_be_payer` | 400 | The transaction source is the facilitator's wallet | You cannot transfer tokens from the facilitator's wallet |
| `facilitator_cannot_be_authority` | 400 | The facilitator is set as the transfer authority | The payer must be the authority for the token transfer |
| `facilitator_ata_cannot_be_source` | 400 | The source ATA belongs to the facilitator | You cannot transfer tokens from the facilitator's token account |
| `facilitator_in_instruction_accounts` | 400 | The facilitator (fee payer) is listed in an instruction's accounts | The facilitator may only be the fee payer: don't pass its address to any instruction, memo included |

### Payer validation errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `cannot_determine_payer` | 400 | Unable to determine the transaction payer from the transfer instruction | Ensure the transfer instruction has a valid source account |
| `invalid_payer_address_format` | 400 | The payer address is not a valid Solana public key | Use a valid base58-encoded Solana public key |

### Memo validation errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `missing_required_memo` | 400 | Transaction is missing a required Memo instruction | Add a Memo instruction for payment attribution |
| `empty_memo` | 400 | The Memo instruction contains no data | Include meaningful memo content for payment tracking |
| `memo_too_long` | 400 | The memo exceeds the maximum allowed length | Reduce memo length (maximum varies by configuration) |
| `invalid_memo_format` | 400 | The memo format is invalid | Ensure the memo follows the required format for payment attribution |

### Transaction lifecycle errors

| Code | HTTP | Description | Resolution |
|------|------|-------------|------------|
| `blockhash_expired` | 400 | The transaction's recent blockhash has expired | Generate a new transaction with a fresh blockhash (blockhashes expire after ~60-90 seconds) |
| `transaction_signature_already_used` | 400 | The network already processed this transaction. Only a settle can hit it, and it is reported as `settlement_pending` (409), since the transaction was executed | Check the previous result or the transaction on chain |
| `simulation_failed` | 400 | Transaction simulation failed before submission | Check that the payer has sufficient SOL for fees and sufficient tokens for the transfer |
| `submission_failed` | 400 | The transaction was rejected before broadcast, or failed on chain (then `transaction` is included and the failure is final). A confirmation timeout is reported as `settlement_pending` instead | Without `transaction`: fix the cause and retry. With `transaction`: a new payment is needed |

### Settlement errors

`settle_timeout` and `settle_confirmation_failed` are no longer returned: a settlement whose confirmation times out or can't be read is reported as `settlement_pending` (409, `Retry-After: 30`, `transaction` when known). See [Idempotency and settlement codes](#idempotency-and-settlement-codes-verify-settle).

---

## Troubleshooting common errors

### simulation_failed

**Symptoms:** Transaction verification returns `simulation_failed` before the transaction is submitted.

**Common causes:**

1. **Insufficient SOL balance** - The payer needs SOL to pay transaction fees (even though the facilitator is the fee payer, simulation may fail if accounts are not properly funded)
2. **Insufficient token balance** - The source account doesn't have enough tokens
3. **Invalid account states** - One or more accounts in the transaction are in an invalid state

**Resolution:**
```bash
# Check payer's SOL balance
solana balance <PAYER_ADDRESS>

# Check token balance
spl-token balance <TOKEN_MINT> --owner <PAYER_ADDRESS>
```

### capability_not_enabled

**Symptoms:** Request returns `capability_not_enabled` even though you expect the capability to work.

**Common causes:**

1. **Capability not configured** - The network/scheme/asset combination hasn't been enabled for your account
2. **Wrong network identifier** - Using mainnet ID when your account is configured for devnet (or vice versa)
3. **Wrong asset address** - Using an incorrect token mint address

**Resolution:**
1. Log in to [console.kobaru.io](https://console.kobaru.io)
2. Navigate to **Settings > Capabilities**
3. Verify the correct network, scheme, and asset are enabled
4. For Solana devnet testing, use: `solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1`
5. For Solana mainnet, use: `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`

### invalid_fee_payer

**Symptoms:** Transaction is rejected because the fee payer is incorrect.

**Common causes:**

1. **Wrong fee payer set** - The transaction specifies a fee payer other than the Kobaru facilitator
2. **SDK misconfiguration** - The x402 SDK is not correctly configured to use Kobaru as the facilitator

**Resolution:**

When constructing your transaction, set the fee payer to the Kobaru facilitator's public key. The facilitator public key is returned in the `/supported` response.

```typescript
// Example: Setting fee payer correctly
const transaction = new Transaction();
transaction.feePayer = new PublicKey(facilitatorPublicKey);
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
```

### blockhash_expired

**Symptoms:** Transaction is rejected because the blockhash has expired.

**Common causes:**

1. **Stale transaction** - The transaction was created too long ago (blockhashes expire after ~60-90 seconds)
2. **Slow client** - Network latency caused delays between transaction creation and submission

**Resolution:**

1. Create transactions with fresh blockhashes immediately before submission
2. Use `getLatestBlockhash()` with the `finalized` commitment for the most reliable blockhashes
3. Consider using durable nonces for transactions that need longer validity

```typescript
// Get a fresh blockhash right before creating the transaction
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('finalized');
transaction.recentBlockhash = blockhash;
```

### duplicate_request

**Symptoms:** Request returns `duplicate_request` when you believe it's a new request.

**Common causes:**

1. **Retry collision** - A previous request with the same signature is still being processed
2. **Reused transaction** - The same signed transaction was submitted multiple times

**Resolution:**

1. Wait for the original request to complete before retrying
2. If retrying, the API will return the cached result for the same idempotency key
3. For new payment attempts, create a new transaction with a fresh blockhash and signature

### priority_fee_exceeds_limit

**Symptoms:** Transaction is rejected due to excessive priority fees.

**Common causes:**

1. **Priority fee too high** - The compute unit price results in total fees exceeding the facilitator's limit
2. **Automatic fee estimation** - Some SDKs automatically set high priority fees during network congestion

**Resolution:**

The Kobaru facilitator enforces a maximum priority fee (default: 100,000 lamports) to prevent abuse. Calculate your priority fee:

```
Total Priority Fee = Compute Units * Compute Unit Price / 1,000,000
```

Reduce either the compute unit limit or the compute unit price to stay within limits.

---

## Error code quick reference

### By category

**Validation errors:** `invalid_x402_version`, `invalid_payload`, `invalid_payment_requirements`, `invalid_network`, `unsupported_scheme`

**Solana amount/recipient:** `invalid_exact_solana_payload_authorization_value`, `invalid_exact_solana_payload_recipient_mismatch`, `invalid_asset_mismatch`

**Transaction format:** `invalid_transaction_format`, `transaction_too_large`, `malformed_instruction`

**Compute budget:** `missing_compute_budget_limit`, `missing_compute_budget_price`, `invalid_compute_unit_limit`, `priority_fee_exceeds_limit`

**Instruction topology:** `invalid_instruction_count_topology`, `invalid_instruction_topology_expected_create_ata`, `invalid_instruction_topology_ata_mismatch`, `missing_transfer_instruction`, `unsupported_token_program`

**Security:** `facilitator_cannot_pay_ata_creation`, `facilitator_cannot_be_payer`, `facilitator_cannot_be_authority`, `facilitator_ata_cannot_be_source`, `facilitator_in_instruction_accounts`

**Concurrency and idempotency:** `duplicate_request`, `settlement_pending`, `claim_unavailable`, `idempotency_conflict`, `payment_already_settled`, `payment_settlement_failed`, `payment_proof_mismatch`, `transaction_signature_already_used`

**Proxy only:** 503 `duplicate_request` / `claim_unavailable` / `settlement_pending` / `server_configuration_error` / `unexpected_verify_error` / `rpc_unavailable` / `bite_unavailable`; 402 `payment_settlement_failed`, `payment_expired`, `payment_already_used`, `payment_bound_to_other_route`, `usage_exhausted`, `payment_proof_mismatch`

**Server:** `unexpected_verify_error`, `unexpected_settle_error`, `server_configuration_error`, `rpc_unavailable`, `bite_unavailable` (`settle_timeout` and `settle_confirmation_failed` are no longer returned)

---

## Getting help

If you encounter an error not covered in this reference or need additional assistance:

- **Documentation**: [docs.kobaru.io](https://docs.kobaru.io)
- **Support**: [support@kobaru.io](mailto:support@kobaru.io)
- **Discord**: Join our developer community for real-time help

When contacting support, include:
1. The full error response (including `invalidReason`)
2. Your request payload (redact sensitive information)
3. The timestamp of the request
4. Your client ID (available in the Console)
