Bag Docs
API Reference

Error Codes

Complete reference of Bag API error codes with HTTP statuses, causes, and how to fix them.

Error Codes

Every error response from the Bag API follows the same shape:

{
  "status": "error",
  "message": "Human-readable description of what went wrong",
  "code": "MACHINE_READABLE_CODE"
}

The message field is safe to display to end users. The code field is stable and safe to match against in your code. Some errors include an additional hint field with specific guidance.


Authentication errors

UNAUTHORIZED

HTTP status401
When it happensMissing, malformed, expired, or revoked API key
{
  "status": "error",
  "message": "Invalid or revoked API key",
  "code": "UNAUTHORIZED",
  "hint": "Include a valid API key in the Authorization header: Bearer bag_live_sk_..."
}

Common causes and fixes:

  • Missing Authorization header — Add Authorization: Bearer bag_test_sk_... to your request headers.
  • Malformed key — Keys must start with bag_live_sk_ or bag_test_sk_. Check for trailing whitespace or truncation.
  • Key was revoked — Generate a new key from the Bag Dashboard under Developer Settings.
  • Expired key — If your key has an expiration date, create a new one.
  • Too many failed attempts — After 10 failed auth attempts from the same IP within 5 minutes, further attempts are blocked. Wait and retry.

FORBIDDEN

HTTP status403
When it happensValid credentials but insufficient permissions for the requested action
{
  "status": "error",
  "message": "Cannot delete files outside your directory",
  "code": "FORBIDDEN"
}

Common causes and fixes:

  • Accessing another merchant's resources — You can only access resources belonging to your merchant account.
  • Deleting files outside your directory — The upload delete endpoint restricts deletions to your own merchant directory.
  • Using a test key for live-only operations — Some operations require a live API key.

Validation errors

BAD_REQUEST

HTTP status400
When it happensRequest body is malformed, missing required fields, or contains invalid values
{
  "status": "error",
  "message": "amount: Amount must be a positive number up to 999,999,999.99; network: Invalid enum value",
  "code": "BAD_REQUEST"
}

The message field contains semicolon-separated validation errors in the format field: reason. When using the SDK, these are parsed into structured field errors.

Common causes and fixes:

  • Missing required fields — Check the API reference for which fields are required. For example, POST /api/payment-links requires name, amount, and network.
  • Invalid enum value — Fields like network and token only accept specific values. See the endpoint documentation for valid options.
  • Amount out of range — Amounts must be between 0.01 and 999,999,999.99.
  • Invalid wallet address — Wallet addresses must be 26–128 alphanumeric characters.
  • Invalid transaction hash — Transaction hashes must be 32–128 hex characters, optionally prefixed with 0x.
  • Invalid URL — Webhook URLs must be valid HTTPS URLs under 2048 characters.
  • Invalid email — Email addresses must be valid and under 320 characters.
  • Key-network mismatch — Test keys (bag_test_sk_*) can only be used with testnet networks (base_sepolia, eth_sepolia, solana_devnet). Live keys (bag_live_sk_*) can only be used with mainnet networks (base, ethereum, polygon, solana).
  • Mode mismatch — A transaction's payment link must be in the same mode as the API key being used. You cannot record a transaction against a live payment link using a test key, or vice versa.
  • Testnet settlement — Settlements are not available for testnet networks. Only live-mode transactions can be settled.

Payment errors

CONFLICT

HTTP status409
When it happensDuplicate resource or state conflict
{
  "status": "error",
  "message": "Transaction already recorded",
  "code": "CONFLICT"
}

Common causes and fixes:

  • Duplicate transaction hash — The txHash you submitted is already associated with another transaction. Each on-chain transaction can only be recorded once. If you're retrying after a timeout, check GET /api/transactions first to see if the original request succeeded.
  • Duplicate KYB submission — You can only submit one KYB application. Use PATCH /api/kyb to update an existing application.
  • Duplicate checkout submission — The session already has a transaction hash attached. Check the session status with GET /api/checkout/session/{id}.

NOT_FOUND

HTTP status404
When it happensThe requested resource does not exist or has been deleted
{
  "status": "error",
  "message": "Not found",
  "code": "NOT_FOUND"
}

Common causes and fixes:

  • Invalid ID — Double-check the resource ID in your request path or query parameter. IDs are UUIDs (e.g., d4e5f6a7-b8c9-4d0e-a1f2-b3c4d5e6f7a8).
  • Resource was deleted — Payment links, webhook endpoints, and API keys can be permanently deleted.
  • Expired checkout session — Sessions expire 30 minutes after creation. Create a new session.
  • Wrong environment — Resources created with a test key are not visible with a live key, and vice versa.

Rate limiting

RATE_LIMITED

HTTP status429
When it happensToo many requests in the current time window
{
  "status": "error",
  "message": "Too many requests. Please slow down.",
  "retryAfter": 10
}

The retryAfter field indicates how many seconds to wait before retrying. The response also includes rate limit headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the current window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp (ms) when the window resets

How to fix: Implement exponential backoff with jitter. See the Rate Limits page for detailed retry guidance and code examples.


Server errors

INTERNAL_ERROR

HTTP status500
When it happensUnexpected server-side failure
{
  "status": "error",
  "message": "Internal server error",
  "code": "INTERNAL_ERROR"
}

How to fix: This is a Bag-side issue. Retry the request after a short delay (1–2 seconds). If the error persists, check the Bag status page or contact support.


TIMEOUT

HTTP status504
When it happensAn upstream service (e.g., tax calculation) did not respond in time
{
  "status": "error",
  "message": "Tax calculation timed out. Please retry.",
  "code": "TIMEOUT"
}

How to fix: Retry the request after 2–5 seconds. Timeouts are typically transient. If the tax quote endpoint consistently times out, the Numeral tax service may be experiencing issues.


Handling errors in code

import { Bag, BagError } from "@getbagsapp/sdk";

const bag = new Bag({ apiKey: process.env.BAG_API_KEY! });

try {
  const link = await bag.paymentLinks.create({
    name: "Pro Plan",
    amount: 29.99,
    network: "base",
  });
} catch (err) {
  if (err instanceof BagError) {
    switch (err.code) {
      case "UNAUTHORIZED":
        console.error("Check your API key");
        break;
      case "BAD_REQUEST":
        console.error("Validation failed:", err.message);
        break;
      case "RATE_LIMITED":
        const retryAfter = err.retryAfter ?? 10;
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        break;
      default:
        console.error("API error:", err.code, err.message);
    }
  }
}
from bag import Bag, BagError
import time

bag = Bag(api_key=os.environ["BAG_API_KEY"])

try:
    link = bag.payment_links.create(
        name="Pro Plan",
        amount=29.99,
        network="base",
    )
except BagError as e:
    if e.code == "UNAUTHORIZED":
        print("Check your API key")
    elif e.code == "BAD_REQUEST":
        print(f"Validation failed: {e.message}")
    elif e.code == "RATE_LIMITED":
        retry_after = getattr(e, "retry_after", 10)
        time.sleep(retry_after)
    else:
        print(f"API error: {e.code} - {e.message}")

Error code summary

CodeHTTP StatusDescription
BAD_REQUEST400Invalid request body or parameters
UNAUTHORIZED401Missing or invalid authentication
FORBIDDEN403Insufficient permissions
NOT_FOUND404Resource does not exist
CONFLICT409Duplicate resource or state conflict
RATE_LIMITED429Too many requests
INTERNAL_ERROR500Unexpected server error
TIMEOUT504Upstream service timeout

On this page