Catalog API Support

Common questions and answers about using CatalogAPI.

Jump to a question (12)

General Questions

How long is a typical integration?

An integration can be complete in a day. We would recommend a 2-week turn-around from concept to live catalog to allow for design, implementation, and testing.

Are there options for international catalogs?

Catalog API offers a global rewards service with catalog offerings in 120+ countries. Click here to see the redemption options available.

Can I have more than one catalog attached to my site?

You can have as many catalogs as you need. They can be created by demographic, country, type of awards, value of awards, or any way that you can define one. You just provide the business rules, and we implement the catalog to your requirements.

How often will the merchandise selection in my catalog be updated?

The catalog selection will be refreshed about every 12 hours. Our proprietary system is constantly stocking your catalog with new items based on popularity and availability.

Where will items ship from?

We source from in-market suppliers. Over 80 suppliers are already integrated and available with the Catalog API service. Some markets may require regional fulfillment / shipping services. Catalog API handles all fulfillment responsibilities so the reward gets to the recipient efficiently.

Are there rewards in languages other than English?

Yes. Not only do we provide translation services, but many suppliers also provide localized language product listings that you can use to promote your rewards.

How do I authenticate API requests?

CatalogAPI authenticates with a long-lived API key that you generate in your dashboard. Paste that key into the Authorization header of every request.

We receive it using the standard HTTP Bearer scheme, so the key goes after the word Bearer in the header.

Example:

curl -H "Authorization: Bearer YOUR_API_KEY" \
 https://api.catalogapi.com/api/v2/sockets

Environment endpoints:

  • Production: https://api.catalogapi.com/api/v2/
  • Sandbox: https://api.catalogapi.com/sandbox/api/v2/

Getting a 401 Unauthorized? Work the checklist:

  1. Are you calling the matching environment? Sandbox API keys only work against /sandbox/api/v2/*. Production API keys only work against /api/v2/*. Swapping them silently 401s.
  2. Authorization header shape. Exactly Authorization: Bearer YOUR_API_KEY, including the word "Bearer" and a single space. No "Token" prefix, no quotes around the key.
  3. Whitespace on the key. Copy-paste often grabs trailing newlines. Trim before sending.
  4. API key active? Visit your dashboard and make sure the key still shows "Active." Deactivated keys stop working immediately.
  5. Email confirmed and MFA passed? You can't generate a working API key until your email is confirmed. If MFA is enabled, your login session needs it verified.

If all five check out and you're still seeing 401, send us the API key's name (never the key itself) and the exact timestamp of a failed request and we can trace it.

What's the difference between sandbox and production?

Sandbox is a full-featured clone of the production API with a separate API key pool. Use it for all development and testing. You won't affect real orders, suppliers, or billing.

Sandbox Production
Base URL /sandbox/api/v2/ /api/v2/
API keys Sandbox-only (separate pool) Production-only
Orders placed Simulated end-to-end: no shipments, no billing Real: fulfilled by suppliers, billed to your account
Status updates Simulated (fake tracking numbers, fake status transitions) Real from suppliers
Catalog Identical to production: same items, same pricing Same catalog; items actually fulfill

Moving to production

Once your sandbox integration works end-to-end, complete payment setup in the dashboard, generate a production API key, and swap the base URL + API key in your app. Everything else stays the same. The API surface is identical.

What are the API rate limits?

Default limits apply per customer account across all API keys for that account:

  • 1,000,000 requests per hour
  • 100,000 requests per minute

Every response includes these headers so you can self-throttle:

  • X-RateLimit-Limit: your current limit
  • X-RateLimit-Remaining: how many calls are left in the window
  • X-RateLimit-Reset: Unix timestamp when the window resets

If you exceed a limit you'll receive HTTP 429 "Too Many Requests." Back off exponentially and respect X-RateLimit-Reset rather than retrying immediately. Contact support if your use case needs higher limits.

How do I rotate API keys without downtime?

CatalogAPI supports zero-downtime API key rotation by allowing up to 2 active API keys per environment (sandbox and production). This lets you safely rotate credentials without interrupting service.

Step-by-step:

  1. Generate a new API key. In your dashboard, click "New API Key" for the environment you want to rotate. Give it a descriptive name like "Production Key - Dec 2024" so you can find it later. You can have at most 2 API keys per environment at a time.
  2. Deploy the new API key. Roll it out to your apps, scripts, and other systems. Take your time, both API keys keep working. Always rehearse in sandbox first.
  3. Verify the deployment. Check your logs and monitoring that calls are succeeding with the new API key.
  4. Deactivate the old API key. Click the pause icon next to the old API key. Deactivation is immediate, with no grace period.
  5. Watch for failures. If anything breaks, click play to reactivate while you fix the deployment.
  6. Clean up (optional). Leave the old API key deactivated for a few days as a safety net, then delete. A 90-day rotation cadence is a reasonable default.

How does MFA work, and what if I lose my authenticator?

Portal accounts can opt into TOTP-based two-factor authentication (the kind of 6-digit code you get from Google Authenticator, 1Password, Authy, etc.). Enable it from Settings → Security.

How login works with MFA enabled

  1. Enter your email and password on the login page.
  2. If we verify the password, we ask for your current 6-digit authenticator code.
  3. Once the code is verified, you're fully logged in.

I lost my phone / authenticator. What do I do?

Use the form at the bottom of this page (or email the support address) and include your account email for help.

Tip: When you first enroll, save the setup secret in a password manager in addition to the authenticator app. That gives you a second way to recover without contacting us.

Why is /cart/validate rejecting my cart?

POST /cart/validate runs a set of pre-flight checks before we'll accept an order. It's the single most common place integration bugs surface, and the single most important thing to call before every order. Here are the most common rejections and what they mean.

Stale prices

The price of one or more items in the cart has changed since they were added. Re-read the cart, show the user the new totals, and let them re-confirm before retrying validate + order.

Out of stock / unavailable

An item that was available when added is no longer available. Remove it (or let the user choose an alternative) and revalidate. Don't hide the error. Users appreciate knowing before they try to check out.

Address missing or invalid

You need to call PUT /sockets/{id}/cart/address before validate. If you already did and it's still failing, the address failed supplier validation, usually a typo in the Postal Code or a PO box for an item that can't ship to PO boxes.

Region mismatch

Some items can't ship to the address's country/region. The error response will identify which line items are affected so you can remove them or change the shipping address.

Cart version conflict

You passed a stale cart_version; another request already modified the cart. Re-read the cart (you'll get the current version back), then retry.

Every validate failure includes error.code and error.details in the response body identifying exactly which line items and fields triggered it. Show the structured error to the user rather than a generic "something went wrong."

The same list lives in the Cart Management documentation under "Why validate Rejects a Cart", which is also what we publish to AI agents.

Are there character limits on address fields?

The API doesn't impose a strict maximum length on address fields, but the fulfillment system that receives your orders stores each field in a fixed-width column. A value longer than the column can cause the order to be rejected or the field to be truncated downstream, so keep address fields within these lengths:

Field Max characters
given_name 60
surname 60
address_1 (required) 75
address_2 60
address_3 60
address_4 60
city (required) 60
state_or_region 60
postal_code (required) 15
country (required) 2 (ISO 3166-1 alpha-2 code)
phone_number 255
email 255

Put the meaningful street address in address_1 through address_4. If a value regularly runs long, abbreviate it before sending (for example "Apt" instead of "Apartment") rather than relying on truncation.

The same table lives in the Cart Management documentation under "Address Field Limits", which is also what we publish to AI agents.

How do cart payments work when placing orders?

When converting a cart to an order, CatalogAPI provides flexible payment tracking fields that allow you to record various payment methods and charges while maintaining clear billing to your account.

Understanding the payment fields

price_paid

The actual amount CatalogAPI charges your account for each item. This is what appears on your invoice. It represents the wholesale cost of the item to you as the API customer.

points_paid

For loyalty program implementations, this tracks if the end user paid using points instead of (or in addition to) currency. This is purely informational; you're still charged price_paid by CatalogAPI. Use it to show users their point redemption history. Even if the user pays entirely in points, you still pay CatalogAPI the price_paid amount in currency.

tax_paid

Records the sales tax you collected from the end user at checkout. This is a pass-through field; CatalogAPI doesn't calculate or collect this tax. You calculate it at runtime and pass it to us for reporting.

If you're running a loyalty program and want to present a points-only catalog to users, we can handle sales tax on our side so you can give users an all-inclusive point price. Ask about this when you're ready to go live.

shipping_paid

Records the shipping fee you charged the end user. Like tax_paid, this is collected by you at runtime and passed through for reporting. Items include a shipping_estimate field from our suppliers which you can show separately or roll into the item price. We typically charge you for shipping based on that same field. Loyalty programs often request a catalog where price_paid already includes shipping so the end user sees no shipping line.

Integration with third-party services

Sales tax calculation

Integrate with a service like TaxJar to calculate sales tax at checkout. Call their API with cart details, collect the tax, and pass it in tax_paid when placing the order.

// Example with TaxJar
const tax = await taxjar.taxForOrder({
  to_zip: shippingAddress.zip,
  to_state: shippingAddress.state,
  amount: cartTotal,
  shipping: shippingAmount
});

// Pass to CatalogAPI
lineItemPayments: [{
  line_item_id: "...",
  price_paid: "100.00",
  tax_paid: tax.amount_to_collect,
  shipping_paid: "10.00"
}]
Payment processing

Use Stripe or a similar processor to collect credit card payments from end users. Pure loyalty programs typically don't need card processing; users pay with points.

// Example with Stripe
const paymentIntent = await stripe.paymentIntents.create({
  amount: totalWithTaxAndShipping * 100,
  currency: 'usd',
  metadata: { catalogapi_order: externalOrderId }
});

// After successful payment, place order with CatalogAPI
// price_paid is what YOU pay CatalogAPI
// The Stripe amount is what the END USER paid you

Important: Regardless of how the end user pays (points, credit card, etc.) or what extra charges you collect (tax, shipping), your account is always billed the price_paid amount for each item. The other fields are for your reporting only.

Example order payload

{
  "cart_version": "abc-123",
  "external_order_id": "ORD-2024-001",
  "line_item_payments": [
    {
      "line_item_id": "item-1",
      "price_paid": "100.00",      // What you pay CatalogAPI
      "points_paid": 5000,          // User paid 5000 loyalty points
      "tax_paid": "8.25",           // You collected $8.25 in sales tax
      "shipping_paid": "10.00"      // You charged $10 for shipping
    }
  ]
}

// Total charged to end user: $118.25 (or 5000 points + $18.25)
// Total you pay CatalogAPI: $100.00
// You keep: $18.25 (tax + shipping) to cover those costs

Do I have to use the cart to place an order?

No. If you already have a storefront, you can skip the catalog and cart endpoints entirely and use CatalogAPI as a fulfillment engine behind it.

Every catalog socket comes with a SQLite file of its catalog. Download it, load it into your own system, and query it the way you query anything else you own. Your search, merchandising, and checkout stay yours. From there you place orders with a single call and follow fulfillment by tracking or webhooks.

The file is regenerated several times a day, so refreshing daily is a sensible default. It arrives as a complete file rather than a delta feed, so you swap the database out rather than reconciling changes into it.

Placing an order this way runs the same validations the cart flow would, including price validation and whether the item requires a shipping address. A snapshot that has drifted since you loaded it fails at order time rather than quietly placing a wrong order.

Read the fulfillment-only guide

An order failed or is stuck. What do the statuses mean?

Once POST /cart/order returns a 2xx, the cart becomes an order and each line item enters our fulfillment pipeline. Status lives on the line item, not the order as a whole. A single order can contain items in different statuses (for example, one fulfilled and one backordered). Read the current state via GET /orders/{external_order_id}.

Line item statuses

  • new: we accepted the item but have not yet sent it to a supplier
  • pending: we are holding the item because your account does not have enough funds on file to cover it. Once billing is resolved it moves on. Contact us if you see this unexpectedly.
  • exported: handed off to the supplier for fulfillment. Our support staff actively monitors items in this status and will follow up regarding merchandise delivery after 10 business days.
  • backordered: the supplier is waiting on stock and we retry automatically as stock returns
  • fulfilled: terminal success. Tracking info (physical) or virtual code (digital) is on the matching fulfillment record, not the line item.
  • cancelled: terminal. The item will not fulfill and you will not be billed for cancelled items.
  • rejected: terminal. The supplier declined the item. Our support team handles these with the supplier directly. If we need information from your end user (for example, to fix an address), we will reach out to you.

Where tracking and virtual codes live

Line items don't carry tracking fields themselves. When a line item becomes fulfilled, look at the fulfillments array on the order. Each fulfillment record has a line_item_ids list pointing back to the items it covers, so you can render the right info next to the right item:

  • Physical items: shipper (carrier) and tracking_number.
  • Digital items: virtual_code_link (and virtual_code_pin when applicable). These are what the end user redeems.

One fulfillment can cover multiple line items (combined shipments), and one order can have multiple fulfillments (partial shipments). Plan your UI accordingly.

When is an order actually placed?

Only after POST /cart/order returns a 2xx. If you got a non-2xx, no order exists, so it is safe to retry after fixing the cause. If the network dropped mid-call and you're not sure, look up the order by your external_order_id; idempotency means we won't double-charge you.

Something's stuck or off

Most items move from new to exported within minutes, and from exported to fulfilled within a business day, though items that require shipping may take longer. Our support staff actively monitors items in exported status and will follow up with you regarding merchandise delivery after 10 business days. If an item sits in pending, that is a billing hold and you can resolve it with us. If an item is stuck in exported past what is reasonable for the supplier, contact support with the external_order_id and we will investigate.

How do I get more help?

In roughly the order we'd suggest trying them:

  • API documentation. Our interactive API docs cover every endpoint with examples.
  • AI integration guide. Our AI agents guide has a copy-paste prompt that gets Claude Code to scaffold an integration for you.
  • Contact us directly. The form below goes straight to our support team, or email support@catalogapi.com . Include the environment (sandbox or production), the endpoint you called, the exact request (minus the API key), and the response we sent you. The more specifics, the faster we can help.

Once you're a customer

Support is tiered. Your team fronts your own users, and anything to do with an order escalates to us. When you onboard, we set your named contacts up in our ticketing system, so they raise issues with our team directly and can see progress on them, rather than emailing into a general queue. Response times are covered by your agreement with us. Before you have a contract in place, technical questions go to support@catalogapi.com .

We work rejected and stuck order items with the supplier directly, and come to you only when we need something from your end user, such as a corrected address. See the order status documentation for what each status means and who is doing what.

Still need help?

Can't find your answer above? Send us a message and we'll help you out.