API Reference

OrderAtlas REST API

Complete reference for programmatic order placement, tracking, and management.

Base URLhttps://api.orderatlas.net
Authentication: Include X-API-Key: oatl_live_... in every request. Generate API keys from your dashboard after signing in.
Credits: Every authenticated JSON response includes a credits_remaining field showing your current balance after the request. Use it to monitor your quota without an extra API call. Example: {"success": true, "order_id": "...", "credits_remaining": 998}.

Supported Platforms

Pass the platform identifier in the platform field of your API requests. The Convert badge marks marketplaces where Aquiline tracking conversion is available via POST /v1/order/:id/convert.

AmazonUSAmazon US (amazon.com)LiveConvert
AmazonUKAmazon UK (amazon.co.uk)LiveConvert
AmazonCAAmazon Canada (amazon.ca)LiveConvert
AmazonMXAmazon Mexico (amazon.com.mx)Live
AmazonDEAmazon Germany (amazon.de)LiveConvert
AmazonFRAmazon France (amazon.fr)LiveConvert
AmazonITAmazon Italy (amazon.it)LiveConvert
AmazonESAmazon Spain (amazon.es)LiveConvert
AmazonNLAmazon Netherlands (amazon.nl)Live
AmazonBEAmazon Belgium (amazon.com.be)Live
AmazonPLAmazon Poland (amazon.pl)Live
AmazonSEAmazon Sweden (amazon.se)Live
AmazonIEAmazon Ireland (amazon.ie)Live
AmazonAUAmazon Australia (amazon.com.au)Live
AmazonINAmazon India (amazon.in)LiveConvert
AmazonSGAmazon Singapore (amazon.sg)Live
AmazonJPAmazon Japan (amazon.co.jp)Live
AmazonBRAmazon Brazil (amazon.com.br)Live
AmazonTRAmazon Turkey (amazon.com.tr)Live
AmazonEGAmazon Egypt (amazon.eg)Live
AmazonZAAmazon South Africa (amazon.co.za)Live
AmazonSAAmazon Saudi Arabia (amazon.sa)Live
AmazonAEAmazon UAE (amazon.ae)Live

Order Statuses

All possible values for the status field returned in order responses and webhook payloads.

confirmedOrder placed successfully, awaiting shipment from the retailer.
purchasedPayment processed and order confirmed on the platform. Equivalent to confirmed for most workflows.
shippedOrder has shipped. Tracking number and carrier are available.
out_for_deliveryPackage is out for delivery and should arrive today.
deliveredPackage has been delivered to the shipping address.
cancelledOrder was cancelled by the buyer, seller, or platform.
lostPackage was lost in transit or could not be located by the carrier.
failedOrder placement failed due to a platform error, payment issue, or invalid credentials.
PRICE_EXCEEDEDSubtotal exceeded the price_max limit. Order was not placed.
refundedOrder has been refunded by the retailer.
payment_revisionPlatform requires a payment method update. The order is on hold until resolved.
awaiting_approvalOrder requires manual approval on the platform (e.g., high-value items or identity verification).
warningOrder was placed but the platform order ID could not be retrieved. May require manual verification.

Common Failure Reasons

Strings you'll see in the error field of a failed order — and how to resolve each. The full reason is also stored on the order's notes column and surfaced in GET /v1/order/:id.

For programmatic branching, every failed order also carries a machine-readable failure_reason_code on GET /v1/order/:id — values match the bolded codes below (e.g. SHIPPING_RESTRICTED, PRICE_EXCEEDED, ACCOUNT_SOFTBLOCK, DIGITAL_PRODUCT_UNSUPPORTED, ASIN_SWAPPED, LOGIN_FAILED, CHECKOUT_RPC_ERROR, STALE_PLATFORM_ORDER_ID, GATEWAY_RESTART_INTERRUPTED). Branch on the code instead of substring-matching the notes — the codes are stable, the human strings may rewrite as we improve diagnostics.

Amazon requires CVV / security code re-entry on this accountThe Amazon account has "require security code on every order" enabled. Amazon's CVV widget RSA-encrypts client-side via secureFields, so the API can't supply the code at checkout time. Two fixes: (1) disable the setting at amazon.com → Account → Login & security → Payment options → Security code preferences; or (2) include the payment_method block on POST /v1/order — we'll add a fresh card to the wallet as default and place the order against it before the CVV check fires.
amazon required additional verification (CVF / one-time-passcode verification)Amazon's risk engine flagged this login attempt and demanded an OTP verification we can't pass. Usually transient — retry in a few minutes; the same account often succeeds on the next try. Persistent CVF on a single account suggests Amazon flagged the proxy or the account itself.
amazon required additional verification (captcha challenge)The login redirected to opfcaptcha.amazon.com. Most flavors are auto-solved on the next checkout retry; persistent captcha means the proxy or session is heavily flagged. Rotate the proxy.
amazon required additional verification (account-fixup prompt)Amazon wants the account holder to add a phone number or verify an email. Log in to amazon.com manually, complete the prompt, then retry.
place-order step did not create a new orderAmazon de-duplicated this submission against a recent purchase, OR the place-order POST silently failed. Credits are auto-refunded. Either Amazon already placed the order (check the account's order history) or the placement failed and you can retry — they're distinguishable by whether a new order ID appears on Amazon.
BOT_CHALLENGE_RX_SHELLAmazon served the JS-fingerprint shell page. Gateway auto-relogs and retries once. If it persists, the proxy / session is flagged at the IP layer — rotate to a fresher proxy.
SESSION_FLAGGED_AT_PLACEAmazon's risk filter bounced the place-order step to a captcha page, sign-in form, or account-locked banner. Gateway auto-retries once on a forced-fresh login (drops the cached session, runs full password + 2FA). If the fresh-session attempt also flags, the result is rewritten to ACCOUNT_SOFTBLOCK (see below). Detection is shape-specific now — looks for real captcha endpoints (/errors/validateCaptcha) and real sign-in forms, not just substring matches on the persistent nav widget.
SESSION_FLAGGED_NO_CSRFThe SPC checkout page rendered without a CSRF token, which means Amazon's risk engine stripped the form rather than serving an explicit captcha. Same auto-retry path as SESSION_FLAGGED_AT_PLACE: fresh login + one retry, then escalates to ACCOUNT_SOFTBLOCK if the fresh session also gets stripped.
OOS_AFTER_RETRIESThe place-order POST returned Amazon's 1187-byte turbo "out of stock" micro-page 15 times in a row on the same session. Often a session softblock rather than a real stockout — Amazon serves the OOS template to flagged sessions while the item is still buyable on a fresh login. Gateway auto-retries on a forced-fresh session before terminalizing. If the fresh-session attempt also returns the OOS shell, it's escalated to ACCOUNT_SOFTBLOCK.
ADDRESS_RATE_LIMITEDTerminal for ~2 hours on the affected account. Amazon's address-change widget responded with "You have exceeded the maximum attempts allowed, please retry after 2 hours." — a per-account throttle that fires after too many shipping-address changes in a short window. Typical trigger: rapid back-to-back orders on the same Amazon account where each placement re-changes the buyer's ship-to. The throttle is persistent per Amazon's message; fresh-login retry doesn't help (it's account-scoped, not session-scoped). Retry on a different Amazon account, or wait the full window. Before this status existed (pre-2026-06-05), the same condition cascaded silently into ADDRESS_VALIDATION_FAILED because Amazon's response was a 200 with the rate-limit message in the inline error_messages array — we now detect and surface it precisely.
SPC_UNHYDRATED_AFTER_RETRIESAmazon served the bare a-no-js shell page on the SPC re-fetch after our address change, across multiple retries — no checkout content, no Delivering to block, page chrome only. Same session-softblock pattern as OOS_AFTER_RETRIES at place-order, just one step earlier in the funnel. Gateway auto-retries on a forced-fresh login; if the fresh-session attempt also returns a shell, escalated to ACCOUNT_SOFTBLOCK. Real shells observed in prod span 60-180 KB; the marker-first detector catches them regardless of variant.
ATC_OOS_AFTER_RETRIESSame softblock pattern as OOS_AFTER_RETRIES but caught at the add-to-cart step rather than place-order. Amazon's ATC endpoint returned "out of stock" across 15 attempts with up to 5 distinct refreshed offer IDs from fresh product-page fetches — while the product page itself consistently shows the buybox in stock. Strong signal that Amazon's risk engine is rejecting the SESSION (not the offer). Gateway auto-retries on a forced-fresh login; if the fresh-session attempt also returns ATC OOS, escalated to ACCOUNT_SOFTBLOCK. Genuine OOS would have shown up earlier (the product page wouldn't have yielded a working offer ID in the first place).
ACCOUNT_SOFTBLOCKTerminal — auto-retry won't fire. The fresh-session retry triggered by SESSION_FLAGGED_AT_PLACE / OOS_AFTER_RETRIES / BOT_CHALLENGE_RX_SHELL / SESSION_FLAGGED_NO_CSRF ALSO came back flagged. That means the block is at the account level, not the session — a new login won't clear it. Manually log in to amazon.com in a browser and place one order to clear the flag, then resubmit. The flag usually self-clears within a few hours; if it persists beyond 24h, the account may need a full Amazon-side recovery.
SHIPPING_RESTRICTEDTerminal — only fires after offer rotation also failed. Amazon refused to ship this ASIN from the buybox seller to the customer's address (HAZMAT to PR/AK/HI, marketplace policy, jurisdiction restriction, AMZL-only seller to non-AMZL ZIP). When the order has price_max > 0 set, the gateway auto-rotates: pulls every seller's offer via Amazon's AOD endpoint, filters to those with price ≤ price_max (excluding the failed buybox offer), sorts cheapest-first, and retries checkout against the top 3 alternatives. If any succeeds, the order ends as confirmed with a note in notes[] identifying which alternative offer placed (seller name, price, fulfillment type). SHIPPING_RESTRICTED as the final status means EITHER no alternatives qualified under price_max, OR all rotated alternatives also failed. Submit a different shipping address, raise price_max, or try a different ASIN. Orders without price_max skip rotation entirely (deterministic behaviour — we won't pick an offer at an arbitrary higher price without your ceiling).
DIGITAL_PRODUCT_UNSUPPORTEDTerminal — auto-retry won't fire. The submitted ASIN is a digital-only item (Kindle, Audible, MP3, software download, game code, Streaming/Prime Video, magazine subscription, etc.). Amazon's digital checkout pipeline doesn't bind a ship-to address, so the order has no physical destination and OrderAtlas can't place it. Detected during the product-page fetch from a title-token match (e.g. Kindle Edition, [PC Download], 1 Year Download) or a page-shell class match (digital-purchase-checkout, kindleProductPage). Resolve by submitting the ASIN of the physical edition instead — or removing the SKU from the eBay listing.
ASIN_SWAPPEDTerminal — auto-retry won't fire. Amazon's product page is rendering a different variant than the ASIN you submitted (server-side redirect from OOS to a substitute, or the SKU was merged into a sibling). Placing the order would charge for the wrong item. Resubmit with the canonical ASIN Amazon now serves — see the order's log for the swap target.
Amazon account has no credit or debit card on fileThe wallet has no usable CC/DC — only Loan, Points, or other instruments Amazon won't auto-submit. Add a card via amazon.com → Account → Payment options, OR use the payment_method request field to add one inline at order time.

Webhooks

Set a webhook_url on your API key (or per-order via the webhook_url field in POST /v1/order) to receive real-time notifications. All webhooks are sent as POST requests with Content-Type: application/json and a User-Agent: OrderAtlas-Webhook/1.0 header. Delivery is retried up to 3 times with exponential backoff (5s, 10s, 15s) on failure. Your endpoint should respond with a 2xx status code to acknowledge receipt.

Events

order.createdFires when an order is first created (status: pending). Sent immediately after POST /v1/order succeeds.
order.status_changedFires whenever the order status transitions (e.g. pendingconfirmed, confirmedshipped, shippeddelivered).
tracking.convertedFires when Aquiline tracking conversion succeeds and an AQ tracking number is assigned to the order.
return.initiatedFires when POST /v1/order/:id/return successfully opens a return on Amazon's side and an RMA is issued. Carries return_rma, return_method, return_label_url (raw Amazon URL — requires the account session, treat as fallback), return_label_mirror_url (OA-mirrored CDN URL — prefer this), and return_qr_code_url. Only fires on RETURN_CREATED outcomes; RETURN_NO_FREE_METHOD, RETURN_REASON_REJECTED, and other failures don't emit a separate event so you don't get noisy notifications for transient session issues — those surface in the normal order detail row + notes instead.
tracking.action_requiredFires when the tracking poller tries to refresh an order but the upstream Amazon account session is broken. Three action codes, distinguished by whether there's a programmatic remediation: PASSWORD_RESET_REQUIRED (Amazon forced a password change — cardholder resets it, you PATCH the new one via /v1/order/:id/credentials which auto-repolls stuck orders), ACCOUNT_FLAGGED (risk-engine flag — cardholder signs in from their normal device to clear), and ACCOUNT_LOCKED (informational only — no recovery available; Amazon locked the account, retries don't help, your connector may want to surface to ops and/or mark the order resolved on your side since OA can't refresh its tracking). Order status stays unchanged — the order itself is alive (Amazon already placed/shipped/delivered it), only OA can't refresh tracking until the account is healed.

Payload carries action_code (machine-readable: PASSWORD_RESET_REQUIRED / ACCOUNT_LOCKED / ACCOUNT_FLAGGED), action_message (human-readable instructions for the cardholder), and account_email (which Amazon account is affected — useful when one customer owns multiple).

Deduplication: dedup-keyed on (order_id, action_code). The event fires ONCE when the action code first appears for an order; subsequent poll cycles detecting the same code stay silent so a stuck account doesn't spam the customer every 3 hours. Re-arms when (a) the action code transitions (e.g. PASSWORD_RESET_REQUIRED → ACCOUNT_LOCKED) or (b) a successful poll happens (clears the state, so a future regression re-notifies).

Auto-recovery: when the customer PATCHes new credentials via PATCH /v1/order/:id/credentials on an order that had action_required set, OA automatically clears the action state AND fires an async repoll on up to 10 sibling orders for the same account_email — so the customer doesn't wait for the next 3-hour cycle to know whether the new credentials worked. If they didn't, the webhook fires again with the new (or same) code.
aqualine.skippedFires when the auto-conversion poller bails for a reason the customer can act on (e.g. low credit balance, missing ship-from). Includes skip_reason (machine code), skip_message (human description), and where relevant credits_have/credits_need. Deduped per order — only fires when the reason changes, so a stuck order doesn't spam every 3-hour poll cycle. Cleared on successful conversion so a later regression re-notifies. The same message is also appended to notes on the order so it shows up in GET /v1/order/:id. The poller will keep auto-retrying every ~3 hours; nothing else needs to happen on your side once the blocker (e.g. balance) is resolved.
account.orders.scrapedFires when POST /v1/account/orders/scrape returns. Account-level event (no order_id — these are IDs we just discovered, not OA orders yet). Lets subscribers wire scrape → auto-import without polling. Payload has its own shape: account_email, count, since_date, oldest_order_date, credits_charged, and an orders[] array of lean summaries (platform_order_id, order_date, status, total, ship_to_name, items_count, asin = first item). Subscribers iterate orders[] and decide which IDs to feed to POST /v1/order/import. Skipped on zero-result scrapes.
account.order_detail.scrapedFires when POST /v1/account/order/scrape returns. Account-level event (no per-order webhook URL consulted). One webhook per CALL — subscribers iterate orders[] themselves. Each entry carries the lean summary plus the invoice_mirror_url (OA CDN, 7-day TTL) so subscribers can fan out PDF archival without re-fetching. Failed-id entries are dropped from the webhook (only successful IDs surface); zero-success calls skip the event entirely.

Payload Schema

Every webhook event uses the same JSON shape. Optional fields are only included when populated.

Webhook Payload
{
  "event": "order.status_changed",
  "order_id": "b7e4a8c2-1234-5678-9abc-def012345678",
  "platform": "AmazonUS",
  "platform_order_id": "112-4567890-1234567",
  "status": "shipped",
  "previous_status": "confirmed",
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "aqualine_tracking": "AQ9281736450",
  "timestamp": "2026-03-31T14:30:00Z"
}

On order.status_changed events that transition into status: "failed", the payload also carries failure_reason_code (machine-readable taxonomy, same values as GET /v1/order/:id) and failure_reason(human-readable explanation, the most-recent entry from the order's notes). Example failure payload:

Webhook Payload (failure)
{
  "event": "order.status_changed",
  "order_id": "50e98f9c-9f18-4185-b967-879b86a16630",
  "platform": "AmazonUS",
  "platform_order_id": "",
  "status": "failed",
  "previous_status": "processing",
  "logged_in": true,
  "asin": "B0CWNJ75X1",
  "failure_reason_code": "SHIPPING_RESTRICTED",
  "failure_reason": "Sorry, MONDAY HAIRCARE ORIGINAL Dry Shampoo with Keratin, oil-absorbing for All Hair Types 200 mL can't be shipped to the address you selected. Please remove the item or select another address.",
  "timestamp": "2026-06-04T14:43:11Z"
}

Field Reference

eventEvent type: order.created, order.status_changed, tracking.converted, return.initiated, tracking.action_required, or aqualine.skipped.
action_codeMachine-readable code on tracking.action_required: PASSWORD_RESET_REQUIRED, ACCOUNT_FLAGGED, or ACCOUNT_LOCKED. Only the first two are recoverable — PASSWORD_RESET_REQUIRED means the cardholder resets the password on amazon.com and you PATCH the new one to /v1/order/:id/credentials (which auto-repolls all stuck sibling orders); ACCOUNT_FLAGGED means the cardholder signs in to amazon.com from their normal device to clear the risk flag. ACCOUNT_LOCKED is informational only — Amazon locked the account, retries don't help; your handler should treat this as terminal for tracking purposes and surface to ops.
action_messageHuman-readable instructions on tracking.action_required. Surface to the cardholder verbatim.
account_emailThe Amazon account that needs action on tracking.action_required. Useful when one customer owns multiple Amazon accounts so the message reaches the right cardholder.
return_rmaAmazon RMA code (only included on return.initiated).
return_methodChosen return method's display name, e.g. "UPS Drop Off" (only on return.initiated).
return_label_urlRaw Amazon return label URL — requires the Amazon account session, will 401 from your server. Treat as fallback (only on return.initiated).
return_label_mirror_urlOA-mirrored CDN copy of the label PDF — this is what you should download from. Public, no auth required. Empty when the mirror upload failed (only on return.initiated).
return_qr_code_urlQR code link for dropoff returns — empty for label-based methods (only on return.initiated).
order_idOrderAtlas internal UUID for the order.
platformPlatform identifier (e.g., AmazonUS).
platform_order_idRetailer order ID (e.g., 112-4567890-1234567). Empty until the order is confirmed.
statusCurrent order status. See Order Statuses above.
previous_statusPrevious status (omitted on order.created).
tracking_numberCarrier tracking number (omitted until available).
carrierShipping carrier name (omitted until available).
aqualine_trackingAquiline-converted tracking number (only included after tracking.converted).
skip_reasonMachine-readable code on aqualine.skipped events. Current values: insufficient_credits. Branch on this — the human skip_message wording can change.
skip_messageHuman-readable explanation, also appended to the order's notes array.
credits_haveAccount's current credit balance at the moment of the skip. Only included on insufficient_credits.
credits_needHow many credits the operation required. Only included on insufficient_credits.
retry_eligibletrue when the poller will auto-retry once the blocker clears (e.g. balance topped up). Use to differentiate from terminal failures.
failure_reason_codeMachine-readable failure taxonomy. Only present when status=failed. Same values as the failure_reason_code field on GET /v1/order/:id — see the field reference there for the full code list. Branch on this to drive retry / refund / manual-fulfillment logic.
failure_reasonHuman-readable explanation. Only present when status=failed. The most-recent entry from the order's notes column — same string you'd see in the dashboard. Use this for display to operators; use failure_reason_code for programmatic decisions.
timestampISO 8601 UTC timestamp of the event.

Discord Webhooks

Set discord_webhook_url on your API key to receive events as rich Discord embeds. The same events fire to Discord as to your regular webhook, with color-coded embeds (green for delivered, gold for shipped/out_for_delivery, red for cancelled/failed, blue for others). Discord webhook URLs must match https://discord.com/api/webhooks/... or https://discordapp.com/api/webhooks/....

POST/v1/order

Place an Order

Place a new order on the specified platform. Returns immediately with status=pending while checkout runs asynchronously. Fetch final details (pricing, tracking, status) via GET /v1/order/:order_id or subscribe to order.status_change webhooks. Deducts 2 credits on success, refunded on failure.

Two request shapes — both fully supported, pick whichever fits the call:
1) Single-item — pass product_id, quantity, and price_max at the top level. Routes through Amazon's turbo Buy Now flow (fewer HTTP round-trips, faster placement).
2) Multi-item — pass items as an array; each entry has its own asin, quantity, and price_max. Routes through the regular cart + Chewbacca pipeline (single Amazon order with N line items, one shipping fee). Per-item price guard reads each line's actual cart price from /gp/cart/view.html and aborts the whole order if any line exceeds its cap (no partial-charge surprises). Per-item outcomes (placed / no_offer / asin_swapped / price_exceeded / dropped_post_atc) are persisted to order_items and surfaced in GET /v1/order/:order_id.

Pick by call site, not by recency — neither is going away. Single-item is right when you have one product per order; multi-item is right when the customer is buying a bundle that should ship as one Amazon order.

Per-account FIFO queue. Submissions for the same account_email are serialized in arrival order — back-to-back orders for one Amazon account never run concurrently (Amazon's checkout state is per-account; two parallel checkouts on the same account would corrupt each other's cart). The HTTP POST still returns 202 instantly with status: "pending"; the queued orders run sequentially as upstream orders complete. When you submit while orders are already in flight for the same account, the response's warnings array includes "N order(s) already in flight for account_email=X; this order will run after they finish (FIFO)." so you can see depth without polling. Hard cap of 50 orders per account queue — beyond that you get failure_reason_code: "ACCOUNT_QUEUE_FULL" on the row. Bounded wait of 2 hours; if a queued order doesn't reach the head in that window (the head order is taking forever / wedged) it fails with "ACCOUNT_QUEUE_TIMEOUT". Different account_email values queue independently (no cross-account blocking).

Request
# Single-item shape:
curl -X POST https://api.orderatlas.net/v1/order \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "AmazonUS",
    "product_id": "B09V3KXJPB",
    "quantity": 1,
    "price_max": 50.00,
    "account_email": "[email protected]",
    "account_password": "...",
    "shipping_address": {"name":"John Doe","phone":"5125551234","line1":"123 Main St","city":"Austin","state":"TX","zip":"78701"}
  }'

# Multi-item shape — N line items, one Amazon order, one shipping fee:
curl -X POST https://api.orderatlas.net/v1/order \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "AmazonUS",
    "items": [
      {"asin": "B09V3KXJPB", "quantity": 1, "price_max": 50.00},
      {"asin": "B0CLWJ1423", "quantity": 2, "price_max": 80.00}
    ],
    "account_email": "[email protected]",
    "account_password": "...",
    "shipping_address": {"name":"John Doe","phone":"5125551234","line1":"123 Main St","city":"Austin","state":"TX","zip":"78701"}
  }'

# Full multi-item example with every optional field:
curl -X POST https://api.orderatlas.net/v1/order \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "AmazonUS",
    "items": [
      {"asin": "B09V3KXJPB", "quantity": 1, "price_max": 50.00, "offer_listing_id": "abc..."},
      {"asin": "B0CLWJ1423", "quantity": 2, "price_max": 80.00}
    ],
    "account_email": "[email protected]",
    "account_password": "...",
    "account_2fa_secret": "JBSW Y3DP EHPK 3PXP",
    "proxy": "host:port:user:pass",
    "shipping_address": {
      "name": "John Doe",
      "phone": "5125551234",
      "line1": "123 Main St",
      "line2": "Apt 4B",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    },
    "ship_from": {
      "name": "Your Store",
      "phone": "9735551234",
      "line1": "1500 Industrial Pkwy",
      "city": "Edison",
      "state": "NJ",
      "zip": "08817"
    },
    "payment_method": {
      "mode": "add",
      "credit_card_number": "4111111111111111",
      "holder_name": "John Doe",
      "exp_month": "01",
      "exp_year": "2029",
      "cvv": "123",
      "billing_address": {
        "name": "John Doe",
        "phone": "5125551234",
        "line1": "123 Main St",
        "city": "Austin",
        "state": "TX",
        "zip": "78701"
      }
    },
    "auto_subscribe_save": false,
    "webhook_url": "https://yourapp.com/webhooks",
    "convert_tracking": true,
    "gift": true,
    "gifter_name": "Your Store",
    "gift_message": "Enjoy!"
  }'

# Full single-item example with all optional fields:
curl -X POST https://api.orderatlas.net/v1/order \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "AmazonUS",
    "product_id": "B09V3KXJPB",
    "quantity": 1,
    "account_email": "[email protected]",
    "account_password": "...",
    "account_2fa_secret": "...",
    "proxy": "ip:port:user:pass",
    "shipping_address": {
      "name": "John Doe",
      "phone": "5125551234",
      "line1": "123 Main St",
      "line2": "Apt 4B",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    },
    "ship_from": {
      "name": "Your Store",
      "phone": "9735551234",
      "line1": "1500 Industrial Pkwy",
      "city": "Edison",
      "state": "NJ",
      "zip": "08817"
    },
    "payment_method": {
      "mode": "add",
      "credit_card_number": "4111111111111111",
      "holder_name": "John Doe",
      "exp_month": "01",
      "exp_year": "2029",
      "cvv": "123",
      "billing_address": {
        "name": "John Doe",
        "phone": "5125551234",
        "line1": "123 Main St",
        "line2": "Apt 4B",
        "city": "Austin",
        "state": "TX",
        "zip": "78701"
      }
    },
    "price_max": 50.00,
    "webhook_url": "https://yourapp.com/webhooks",
    "convert_tracking": true,
    "gift": true,
    "gifter_name": "Your Store",
    "gift_message": "Enjoy!"
  }'
Response
{
  "success": true,
  "order_id": "550e8400-e29b-41d4-a716-446655440000",
  "platform": "AmazonUS",
  "status": "pending",
  "message": "Order submitted for processing",
  "warnings": [
    "2 order(s) already in flight for [email protected]; this order will run after they finish (FIFO)."
  ],
  "credits_remaining": 998
}

Request Fields

platform*Platform identifier. See supported platforms above.
product_idSingle-item shape. Product identifier (ASIN for Amazon, SKU for Best Buy). Either product_id or items must be present. When both are sent, items wins and product_id is ignored.
quantitySingle-item shape, paired with product_id. Number of units to order. Defaults to 1 if omitted or ≤ 0. Ignored when items is set (use items[].quantity per line instead).
price_maxSingle-item shape, paired with product_id. Maximum allowed subtotal. If the item subtotal exceeds this amount, checkout is aborted before placing the order. Ignored when items is set (use items[].price_max per line instead). Omit or set to 0 to disable.
itemsMulti-item shape. Array of products, each with its own asin, quantity, and price_max. Either items or product_id must be present; sending both makes items win and the legacy fields are ignored.
items[].asin*ASIN of this line item.
items[].quantityQuantity for this line item. Defaults to 1 if omitted or ≤ 0.
items[].price_maxMaximum allowed cart price (per unit) for this specific line item. The order's /gp/cart/view.html is read after add-to-cart and each line's actual price is checked against its cap. If any line exceeds its cap the entire order is aborted before place-order — no partial purchases. Omit or set to 0 to disable for this line.
account_email*Account email/username for the platform.
account_password*Account password.
account_2fa_secretTOTP 2FA secret (base32 format). Required if the account has 2FA enabled.
proxy*Proxy in format ip:port:user:pass or ip:port. Use residential IPs matching the shipping address region.
shipping_address*Delivery address object. See fields below.
shipping_address.name*Recipient full name.
shipping_address.phone*Recipient phone number.
shipping_address.line1*Street address line 1.
shipping_address.line2Street address line 2 (apartment, suite, etc).
shipping_address.city*City name.
shipping_address.state*State/province code (e.g., TX, CA, ON).
shipping_address.zip*Postal/ZIP code.
ship_fromPer-order ship-from override for the Aquiline tracking label. When omitted, falls back to the account-level ship_from_* from PUT /v1/settings, then a platform default. When provided, applies to this order only (other orders unaffected). Useful for multi-store dropshippers using different return addresses per store.
ship_from.nameStore / sender name.
ship_from.phoneStore phone number.
ship_from.line1Street address line 1.
ship_from.cityCity.
ship_from.stateState / province.
ship_from.zipPostal / ZIP.
payment_methodOptional payment card behavior controlled by payment_method.mode. Default mode "add" runs Amazon's wallet workflow to add this card to the account and use it for the order — needed when Amazon requires CVV re-entry or you want to force a fresh card. Mode "find" matches an existing on-account card by last4 + expiration and uses that instead — needed for cards you cannot add via the wallet workflow (Amazon Prime / Amazon Store Cards have no expiration date on the row) and to avoid cluttering the buyer's saved-cards list with duplicates. Card details are used only for this request and are never persisted, logged, or echoed in webhooks.
payment_method.modeEither "add" (default if omitted) or "find". "add" runs the wallet add-card workflow with the full card number and billing_address. "find" extracts last4 from credit_card_number (caller may pass just the 4 digits) and matches against the account's existing cards by last4 + exp_month + exp_year (or last4 alone when is_store_card=true). In find mode billing_address is ignored — the matched card keeps whatever billing Amazon already has bound to it.
payment_method.credit_card_numberCard number. Required when payment_method is provided. In mode: "find" the full PAN is optional — last4 is sufficient (we extract trailing digits, so "7594", "************7594", and a full PAN all work).
payment_method.holder_nameName on card. Required when payment_method is provided and mode is "add". Ignored in mode: "find".
payment_method.exp_monthTwo-digit expiration month, e.g. 01. Required when payment_method is provided unless mode: "find" AND is_store_card: true.
payment_method.exp_yearFour-digit expiration year, e.g. 2029. Required when payment_method is provided unless mode: "find" AND is_store_card: true.
payment_method.cvvCard security code. Optional, but include it when the account/card flow requires CVV. Only used in mode: "add".
payment_method.is_store_cardOnly meaningful in mode: "find". Set to true for Amazon Prime / Amazon Store Cards. These cards render no expiration date on the payment-selection row, so matching by exp_month + exp_year would always miss them. With this flag, the matcher accepts the card on last4 alone (and only considers cards Amazon classifies as Store Cards).
payment_method.issuer_hintOnly meaningful in mode: "find". Optional case-insensitive substring of the card brand — "Visa", "Mastercard", "Amazon Store Card", etc. Used as a tiebreaker when the account has 2+ cards sharing the same last4 (rare, but possible). When unset and multiple cards match, the order fails with PAYMENT_CARD_AMBIGUOUS so the caller can re-request with a hint instead of silently picking the wrong card.
payment_method.billing_addressBilling address to bind to the card. Only used in mode: "add". If omitted or incomplete in add mode, OrderAtlas uses the order/account ship_from address as the billing address and returns a warning in the order response. Ignored entirely in mode: "find" — the matched card keeps Amazon's existing binding.
payment_method.billing_address.nameBilling full name. If omitted, falls back to ship_from.name.
payment_method.billing_address.phoneBilling phone number. If omitted, falls back to ship_from.phone.
payment_method.billing_address.line1Billing street address line 1. If omitted, falls back to ship_from.line1.
payment_method.billing_address.line2Billing street address line 2.
payment_method.billing_address.cityBilling city. If omitted, falls back to ship_from.city.
payment_method.billing_address.stateBilling state/province code. If omitted, falls back to ship_from.state.
payment_method.billing_address.zipBilling postal/ZIP code. If omitted, falls back to ship_from.zip.
webhook_urlPer-order webhook URL override. If omitted, uses the webhook URL configured on your API key.
convert_trackingPer-order Aquiline conversion override (true/false). If omitted, uses the setting configured on your API key.
auto_subscribe_saveOpt into Amazon's Subscribe & Save flow on this order to capture the (typically 5%) subscription discount. When set to true and the product exposes an S&S option with a non-zero discount, the order is placed at the subscription price and the resulting subscription is auto-cancelled in the same request — no future shipments are scheduled, the first-order discount stays applied. Read back via auto_subscribe_save + subscription_canceled on the order detail. Products without S&S, or S&S with 0% discount, are placed normally. Defaults to false.
giftMark the order as an Amazon gift. After payment selection the checkout sets Amazon's gift options on the order: hidePrices=1 and includeReceipt=1 are always applied so the recipient gets a proper gift receipt with no prices shown. Requires gifter_name. Defaults to false. Best-effort: if Amazon rejects the gift-options form the order still places (without the gift flag) rather than failing the whole checkout.
gifter_nameSender name shown on the gift receipt. Required when gift=true — requests with gift=true and an empty gifter_name are rejected with HTTP 400 before checkout starts.
gift_messageOptional message printed on the gift receipt alongside the sender name. Plain text; Amazon enforces its own length cap.
payment_methodOptional payment card with two modes selected by payment_method.mode. Default mode "add" adds this card to the Amazon wallet before placing the order — use this for accounts with "require security code on every order" enabled (Amazon would otherwise force CVV re-entry the API can't supply). Mode "find" matches an existing on-account card by last4 + expiration and uses it without adding a duplicate — use this for Amazon Prime / Amazon Store Cards (which can't be added via the wallet workflow because they have no expiration date) and for any card already on the account that you don't want duplicated. Sensitive fields are held in memory only — never persisted, never logged, never echoed in webhooks.
payment_method.mode"add" (default if omitted) or "find". See the request-side payment_method.mode entry above for full semantics.
payment_method.credit_card_numberCard number, no spaces. In mode: "find" the full PAN is optional — last4 ("7594") or any masked form ("************7594") also works.
payment_method.holder_nameCardholder name as printed on the card. Only used in mode: "add".
payment_method.exp_monthExpiration month, two digits (e.g., "08"). Required unless mode: "find" AND is_store_card: true.
payment_method.exp_yearExpiration year, four digits (e.g., "2027"). Required unless mode: "find" AND is_store_card: true.
payment_method.cvvCVV / security code. Only used in mode: "add"; required when Amazon's add-card flow asks for it (safe to include preemptively).
payment_method.is_store_cardBoolean, default false. Only meaningful in mode: "find" — set true for Amazon Prime / Amazon Store Cards. Skips the expiration check during matching and restricts candidates to cards Amazon classifies as Store Cards.
payment_method.issuer_hintOptional case-insensitive substring of the card brand ("Visa", "Mastercard", etc.). Only meaningful in mode: "find" — disambiguator when 2+ saved cards share the same last4. Without it, ambiguous matches fail with PAYMENT_CARD_AMBIGUOUS.
POST/v1/order/import

Import Existing Order

Import an order you already placed on the platform into OrderAtlas for tracking and conversion. Costs 1 credit. shipping_address is required — Aquiline tracking needs a ship-to to generate a label.

Request
curl -X POST https://api.orderatlas.net/v1/order/import \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "AmazonUS",
    "platform_order_id": "112-4567890-1234567",
    "account_email": "[email protected]",
    "account_password": "...",
    "account_2fa_secret": "...",
    "proxy": "ip:port:user:pass",
    "shipping_address": {
      "name": "Jane Buyer",
      "phone": "5551234567",
      "line1": "123 Main St",
      "line2": "Apt 4B",
      "city": "Austin",
      "state": "TX",
      "zip": "78701"
    }
  }'
Response
{
  "success": true,
  "order_id": "112-4567890-1234567",
  "status": "confirmed",
  "platform": "AmazonUS",
  "message": "Order imported successfully"
}
POST/v1/order/:order_id/status

Get Order Status

Retrieve the current status, tracking info, and delivery timeline for an order. Requires account credentials for the platform session.

Request
curl -X POST https://api.orderatlas.net/v1/order/112-4567890-1234567/status \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "...",
    "account_2fa_secret": "...",
    "proxy": "ip:port:user:pass"
  }'
Response
{
  "order_id": "112-4567890-1234567",
  "status": "shipped",
  "platform": "AmazonUS",
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "est_delivery_min": "2026-03-28T14:00:00Z",
  "est_delivery_max": "2026-03-30T20:00:00Z"
}
PUT/v1/order/:order_id/tracking

Update Tracking

Push tracking information for an order. Used to provide carrier, tracking number, and delivery events.

Request
curl -X PUT https://api.orderatlas.net/v1/order/112-4567890-1234567/tracking \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "tracking_number": "1Z999AA10123456784",
    "carrier": "UPS",
    "status": "shipped"
  }'
Response
{
  "order_id": "112-4567890-1234567",
  "status": "shipped",
  "tracking_number": "1Z999AA10123456784",
  "carrier": "UPS",
  "message": "shipped"
}
GET/v1/order/:order_id/credentials

Get Order Credentials

Return the stored Amazon account credentials for an order — email, password, 2FA secret, and proxy — decrypted at the API boundary. Useful when an order was placed under a buyer account whose credentials have since changed and you need to confirm what OrderAtlas currently has stored before deciding what to PATCH. Authorization is cross-key within the same OrderAtlas account: any of your active API keys can read credentials on any of your orders. Cross-account attempts return order not found (the existence of orders on other accounts is never disclosed). Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path. The response fields are returned in cleartext — handle them with care (we never log or echo them in webhooks).

Request
curl -X GET https://api.orderatlas.net/v1/order/9677d1cf-03ac-4bb6-acba-d5b933f3099e/credentials \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "account_email":    "[email protected]",
  "account_password": "the-buyer-password",
  "account_2fa":      "JBSWY3DPEHPK3PXP",
  "account_proxy":    "ip:port:user:pass"
}

Request Fields

account_emailAmazon account email currently stored on this order. Empty when the order was placed without an account (e.g. a legacy/imported row).
account_passwordAmazon account password currently stored. Decrypted at this endpoint; ciphertext on the wire to the DB.
account_2faTOTP 2FA secret (base32). Whitespace / dashes / lowercase are tolerated by the login service — the secret normalizer strips them before generating codes.
account_proxyProxy string in host:port:user:pass or http://user:pass@host:port form. Both are accepted; the gateway normalizes before forwarding to downstream services.
PATCH/v1/order/:order_id/credentials

Update Order Credentials

Apply a partial update to the stored Amazon credentials on an order. Each field is optional — omit a field to leave it unchanged. Sending an empty string IS valid and clears the field; this is distinct from omitting the field. Triggered when a customer rotates their Amazon password, 2FA secret, or proxy AFTER the order was placed (which would otherwise break every subsequent /tracking, /convert, and /rescrape that relies on the cached session for that account).

Side effects:

  1. Writes an audit note to notes: "Credentials updated: password, 2fa" — only field names, never values.
  2. Invalidates the cached cookie_string on the order so the next session-dependent operation runs a fresh AmazonLogin with the new credentials rather than continuing to use cookies tied to the old ones.
Returns the post-update credentials so the caller can re-render with the saved state. Authorization is cross-key within the same OrderAtlas account, identical to the GET endpoint. Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path. No credits charged.

Request
curl -X PATCH https://api.orderatlas.net/v1/order/9677d1cf-03ac-4bb6-acba-d5b933f3099e/credentials \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_password": "new-buyer-password",
    "account_2fa": "NEW2FASECRETBASE32"
  }'
Response
{
  "account_email":    "[email protected]",
  "account_password": "new-buyer-password",
  "account_2fa":      "NEW2FASECRETBASE32",
  "account_proxy":    "ip:port:user:pass"
}

Request Fields

account_emailNew Amazon account email. Send to rotate to a different buyer account on the same OrderAtlas account. Most rotations leave this unchanged.
account_passwordNew Amazon account password. Send empty string to clear (rare — typically used during account decommission).
account_2faNew TOTP 2FA secret (base32). Send empty string to disable 2FA for this account. Whitespace / dashes / lowercase are tolerated.
account_proxyNew proxy. host:port:user:pass or URL form both accepted. Send empty string to clear the per-order override (the gateway falls back to the API-key-level proxy if configured, then the service default).
POST/v1/order/:order_id/convert

Convert Tracking Number

Convert a carrier tracking number into a universal tracking number valid for eBay uploads. Costs 1 credit. Only available on the marketplaces Aquiline supports: AmazonUS, AmazonCA, AmazonUK, AmazonDE, AmazonFR, AmazonIT, AmazonES, AmazonIN. Other marketplaces return platform X is not supported by Aquiline. Works as soon as the order is confirmed — Aquiline can be assigned from the order details page even before Amazon publishes a tracking number.

Request
curl -X POST https://api.orderatlas.net/v1/order/112-4567890-1234567/convert \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "order_id": "112-4567890-1234567",
  "tracking_number": "1Z999AA10123456784",
  "aqualine_tracking": "AQUAA4223090426YQ",
  "carrier": "UPS",
  "message": "tracking converted successfully"
}
POST/v1/order/:order_id/drop-conversion

Drop Aquiline Conversion

Detach the current Aquiline tracking from an order so you can re-convert it with corrected data — typically used when an order was imported with a wrong ship-to address and the resulting Aquiline label is shipping the wrong route. The order row stays intact (no change to status, no refund of credits). The previous Aquiline label is moved to dropped_aqualine_tracking on the order; we continue refreshing its tracking HTML in the background until the underlying Amazon package reaches a terminal state (Aquiline TOS requires updates on assigned labels until delivered/cancelled). After dropping, call POST /v1/order/:order_id/convert again to mint a fresh AQUAA against the corrected data.

Credits are NOT refunded. The original conversion ran, the label was issued, the carrier may already be scanning against it. If a customer needs a billing adjustment on top of the drop, contact support — the operation is a tracking reset, not a billing rewind.

Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path.

Request
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf-03ac-4bb6-acba-d5b933f3099e/drop-conversion \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "success": true,
  "order_id": "9677d1cf-03ac-4bb6-acba-d5b933f3099e",
  "dropped_aqualine": "AQUAA6610400526YQ",
  "message": "Aquiline conversion dropped — order is ready to be re-converted. The dropped label will keep refreshing until it reaches delivered/cancelled (per Aquiline TOS)."
}

Request Fields

(no body)No request body. The order is identified entirely by the URL path; the active Aquiline tracking is moved off the row and the order becomes eligible for a fresh convert call.
POST/v1/order/:order_id/rescrape

Rescrape Aquiline Tracking

Force a fresh re-upload of an order's tracking HTML to Aquiline. Used when Aquiline's parser hiccupped on the original upload and the AQUAA appears stuck or misclassified — pushing the HTML again triggers a fresh parse on their side. The order must already have an aqualine_tracking number (call convert first if it doesn't); no credits are charged because the conversion already ran.

How the HTML is sourced: if the order has a valid cookie session we re-fetch the freshest tracking page from Amazon. If the session has expired or fresh fetch fails, we fall back to the last saved HTML on the row (aqualine_last_html) and re-push that — useful when Aquiline's first parse missed something the saved HTML actually contained. The source field in the response says which path was used.

Rate limits (two layers):

  • Per-API-key: the standard rate-limit middleware applies (60 req/min by default). Hitting that returns 429 Too Many Requests from the gateway.
  • Per-order: 10-minute cooldown enforced in the service. Calling again within 10 minutes on the same order returns 400 with rescrape cooldown active — try again in N seconds. The cooldown stops customer scripts from spamming the button at a single stuck order while their per-key minute budget is still healthy.
Authorization is cross-key within the same OrderAtlas account. Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path.

Request
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf-03ac-4bb6-acba-d5b933f3099e/rescrape \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "order_id": "9677d1cf-03ac-4bb6-acba-d5b933f3099e",
  "platform_order_id": "112-4567890-1234567",
  "aqualine_tracking": "AQUAA4223090426YQ",
  "uploaded_at": "2026-06-10T13:45:12Z",
  "html_bytes": 187432,
  "source": "fresh_amazon_fetch",
  "message": "tracking HTML re-uploaded to Aquiline; their parser will re-run within ~60s"
}

Request Fields

(no body)No request body. The order is identified entirely by the URL path.
source (response)Where the uploaded HTML came from. fresh_amazon_fetch means we successfully re-fetched the latest page from Amazon; saved_html means we fell back to the last saved aqualine_last_html on the row (typically because the session cookies were expired and a fresh login wasn't available). Operationally identical from Aquiline's perspective; the source field is purely diagnostic.
html_bytes (response)Size of the HTML body that was pushed to Aquiline. Useful for spotting cases where the fetch returned a small / empty / soft-blocked page (typically under 5KB) instead of the real tracking page (typically 100-300KB).
POST/v1/order/:order_id/refresh-status

Refresh Order Status (manual poll)

Triggers an immediate single-order status poll against Amazon — same machinery as the 3-hour tracking cycle, but on demand. Use when an order is stuck in confirmed and you suspect a 3DS auto-cancel, a shipped state we haven't picked up yet, or any of the silent transitions Amazon makes (delivered without a tracking update, cancellation due to address rejection, etc). No credits charged.

What happens internally:

  1. If the row has no cached session cookies (or they're empty), we log in fresh first using the stored email + password + 2FA secret + proxy on the row. Without that pre-login the monitor service rejects the call with cookie_string is required.
  2. We hit Amazon's /your-orders/order-details?orderID=... page and run our normal status extractor against the response.
  3. If the extracted status differs from the current row, we flip the row, set timestamps (shipped_at/delivered_at/cancelled_at), and fire the order.status_changed webhook.
Terminal short-circuit: if the row is already delivered / cancelled / lost, the call returns the current status instantly without hitting Amazon (those states don't change). Saves a wasted login.

Cooldown: 5 minutes per order. Submitting again within the window returns 400 with refresh cooldown active — try again in N seconds. The cooldown stops a buggy script from burning the account's session budget on a single stuck order.

Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path. Authorization is cross-key within the same OrderAtlas account.

Request
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf-03ac-4bb6-acba-d5b933f3099e/refresh-status \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "order_id": "9677d1cf-03ac-4bb6-acba-d5b933f3099e",
  "platform_order_id": "112-4567890-1234567",
  "status": "delivered",
  "previous_status": "confirmed",
  "changed": true,
  "refreshed_at": "2026-06-17T14:32:18Z",
  "message": "status changed from confirmed to delivered"
}

Request Fields

(no body)No request body. The order is identified entirely by the URL path.
changed (response)Boolean — true when the poll surfaced a status change vs. what was on the row before, false when Amazon's view matched what we already had. Branch on this if you only want to act on changes; ignore otherwise.
status (response)The status that ended up on the row after the poll. Same enum as GET /v1/order/:id.
previous_status (response)What the row's status was BEFORE this refresh call. Useful for change-driven webhooks/UIs.
message (response)Human-readable summary — either "order status checked" (no change) or "status changed from X to Y" (change). Don't parse this — branch on changed and status instead.
POST/v1/order/:order_id/return

Initiate Amazon Return

Programmatically open a return on Amazon's side for a delivered order and get back the RMA, return label, QR code, and drop-off instructions. We drive Amazon's /spr/returns/* flow end-to-end (cart → conversation → resolutions → methods → ledger → contract → confirmation), pick a low-friction reason, choose Refund-to-card, pick the best return method per your priority, finalize the contract, parse the confirmation page. Costs CREDIT_COST_RETURN (default 4) credits per RMA filed — on multi-item orders that's once per item that succeeded. Refunded automatically on transient/our-fault failures (session invalid, upstream errors).

Multi-item orders: by default the endpoint returns every returnable line on the order — one RMA, label, and webhook per item. Optional items: ["B0XXX", "B0YYY"] filters to a subset (ASINs not on the order are silently skipped and logged). Per-item outcomes land in the response's items[] array with each item's own rma_code, label_mirror_url, and status. The top-level rma_code / label_url / etc. fields mirror the first successful item for backward-compat with single-item callers — single-item orders see no change in shape.

Required login fields (all 4): every call must carry account_email, account_password, account_2fa, and account_proxy in the body — even when stored credentials exist. This guarantees the system can transparently recover from a stale or missing session without bouncing the request back to you. account_email must match the order's stored account email (cross-check prevents driving account A's return with account B's creds). Missing any field returns 400 MISSING_LOGIN_FIELDS.

Optional cookie_string: when supplied, skips the session-freshness check entirely and drives the flow against those cookies. The 4 login fields are then the fallback recovery path if Amazon rejects mid-flow.

Reason picker (auto): we hunt for, in priority order — "Not as Expected" → "Defective" → "Damaged" → first option Amazon offers. Never selects "Changed Mind" / "Ordering Issue" / "Bought by Mistake" — those route through Amazon's higher-friction questionnaire and frequently end in a paid label. You can override via reason_preference.

Refund destination (auto): always refund-to-card. The picker prefers paymentInstrumentType=OriginalPurchaserCreditCard → any non-ElectronicGiftCertificate Refund offering. Replacement offerings are never auto-selected.

Method picker (free-first): UPS Store dropoff (UPSDropOff) → Staples dropoff (Staples_AMZL_PackageFree_Dropoff) → UPS labelless dropoff (LabelLess_UPSDropOff) → Whole Foods (WFMUSDropOff) → Amazon Hub → any other free method. If no free method exists, the response is status: "RETURN_NO_FREE_METHOD" with the full paid options list in available_methods — your script can confirm with the customer and re-fire with paid_opt_in: true.

Label PDF mirror: Amazon's label_url is actually a label PREP PAGE (HTML), not a raw file. We follow the page to its embedded /documents/download/<id>/ShipperLabel URL, fetch the real bytes (Amazon serves these as PDF, GIF, or PNG depending on the account), and always normalize to a single-page PDF before mirroring to OA's R2 storage. label_mirror_url is therefore always https://cdn.orderatlas.net/returns/<orderID>/label.pdf (single-item) or https://cdn.orderatlas.net/returns/<orderID>/<ASIN>/label.pdf (multi-item additional items) with Content-Type: application/pdf — no caller-side format branching needed.

Status transition: on success the order's status flips from deliveredrefunded. We fire return.initiated once per successful RMA AND a single order.status_changed (delivered → refunded) regardless of item count. See the Webhooks section.

Eligibility guards: we refuse with 400 if the order isn't delivered (Amazon rejects RMAs on in-flight orders + auto-cancels them, bloating the account's return history) or has no amazon_order_id. Items Amazon marks non_self_serviceable (some auto parts, hazmat, etc.) return RETURN_HTTP_ERROR with a sanitized message — those need a manual return through Amazon's site.

Idempotency: the row's return_rma is the dedupe key. A second call after a successful return returns status: "RETURN_ALREADY_INITIATED" with the existing RMA — no Amazon round-trip, no double-charge.

In-flight gate: while a return is mid-flow for a given order, a second call returns status: "RETURN_IN_PROGRESS" immediately. After the first call finishes — success or failure — the gate releases and you can retry. No fixed wait.

Accepts either the OA order_id (UUID) or the Amazon platform_order_id in the URL path.

Request
# Single-item order — return the only line on the order
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf.../return \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "amazonPassword!",
    "account_2fa": "JBSW Y3DP EHPK 3PXP",
    "account_proxy": "http://user:pass@host:port",
    "paid_opt_in": false
  }'

# Multi-item order — default returns EVERY returnable line on the order
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf.../return \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "amazonPassword!",
    "account_2fa": "JBSW Y3DP EHPK 3PXP",
    "account_proxy": "http://user:pass@host:port"
  }'

# Multi-item — filter to a subset of the order's items
curl -X POST https://api.orderatlas.net/v1/order/9677d1cf.../return \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "amazonPassword!",
    "account_2fa": "JBSW Y3DP EHPK 3PXP",
    "account_proxy": "http://user:pass@host:port",
    "items": ["B09V3KXJPB", "B0CLWJ1423"]
  }'
Response
# Single-item — items[] has 1 entry that mirrors the top-level fields
{
  "order_id": "9677d1cf-03ac-4bb6-acba-d5b933f3099e",
  "platform_order_id": "113-4399153-1222651",
  "status": "RETURN_CREATED",
  "asin": "B0F9Y3649J",
  "rma_code": "DWfryllGRRMA",
  "return_method": "UPSDropOff",
  "return_method_id": "UPSDropOff",
  "label_url": "/spr/returns/label/abc.../?rmaId=DWfryllGRRMA",
  "label_mirror_url": "https://cdn.orderatlas.net/returns/113-4399153-1222651/label.pdf",
  "qr_code_url": "",
  "dropoff_instructions": "Drop your package off at any UPS Store...",
  "is_free": true,
  "fee_amount": "",
  "expiration_date": "Jul 15, 2026",
  "credits_charged": 4,
  "message": "Return initiated via UPSDropOff, RMA DWfryllGRRMA.",
  "initiated_at": "2026-06-20T06:24:41.438Z",
  "items": [
    {
      "asin": "B0F9Y3649J",
      "status": "RETURN_CREATED",
      "rma_code": "DWfryllGRRMA",
      "return_method": "UPSDropOff",
      "label_mirror_url": "https://cdn.orderatlas.net/returns/113-4399153-1222651/label.pdf",
      "is_free": true,
      "expiration_date": "Jul 15, 2026",
      "message": "Return initiated via UPSDropOff, RMA DWfryllGRRMA."
    }
  ]
}

# Multi-item — top-level mirrors first success, items[] has full breakdown
{
  "order_id": "abc-...",
  "platform_order_id": "112-1234567-8901234",
  "status": "RETURN_CREATED",
  "asin": "B0XXXXXXXX",
  "rma_code": "RMA_A",
  "return_method": "UPSDropOff",
  "label_mirror_url": "https://cdn.orderatlas.net/returns/112-1234567-8901234/label.pdf",
  "credits_charged": 8,
  "message": "Return initiated for 2 of 2 items.",
  "items": [
    {
      "asin": "B0XXXXXXXX",
      "status": "RETURN_CREATED",
      "rma_code": "RMA_A",
      "return_method": "UPSDropOff",
      "label_mirror_url": "https://cdn.orderatlas.net/returns/112-1234567-8901234/label.pdf"
    },
    {
      "asin": "B0YYYYYYYY",
      "status": "RETURN_CREATED",
      "rma_code": "RMA_B",
      "return_method": "UPSDropOff",
      "label_mirror_url": "https://cdn.orderatlas.net/returns/112-1234567-8901234/B0YYYYYYYY/label.pdf"
    }
  ]
}

Request Fields

account_email*Amazon account email. Required on every call. Must match the account this order was placed under — driving account A's return with account B's creds is rejected with 400 ACCOUNT_EMAIL_MISMATCH.
account_password*Amazon account password. Required on every call so the system can recover from a stale/missing session without a separate PATCH /credentials round-trip. Used only when the existing session is invalid; otherwise the cached cookies drive the flow.
account_2fa*TOTP seed (base32, spaces OK). Required on every call. If the account doesn't use 2FA, the value is sent through to the login service which simply doesn't challenge — but the field must be present.
account_proxy*Residential proxy URL or host:port:user:pass spec. Required on every call. Same proxy you used when placing the order, ideally — Amazon can refuse returns from a different IP class than the order was placed from.
cookie_stringOptional pre-warmed Amazon session cookies. When supplied, skips the session-freshness check entirely and drives the flow against these cookies. Useful when you already have a fresh login from your own auth flow and want to save a login round-trip.
itemsOptional ASIN filter for multi-item orders. Default behavior (empty / omitted): return EVERY returnable line on the order. When set to ["B0XXX", "B0YYY"], only items whose ASIN matches are processed. ASINs in the filter that aren't on the order are silently skipped (logged operator-side). Single-item orders ignore this field entirely.
paid_opt_inBoolean, default false. When false, we only accept FREE return methods — if Amazon offers only paid options, we abort with status: "RETURN_NO_FREE_METHOD" and the full paid list in available_methods. When true, we fall back to the cheapest paid method when no free option exists. Free methods always win when both are available regardless of this flag — it only changes the fallback. Use this two-pass: first call with false, if you get RETURN_NO_FREE_METHOD show the paid options to the customer for confirmation, then re-fire with true.
reason_preferenceOptional override for the return reason. When empty (default), picks from "Not as Expected" → "Defective" → "Damaged" → first option Amazon offers. Match is case-insensitive substring against Amazon's reason labels. Never use "Changed Mind" / "Ordering Issue" / "Bought by Mistake" — they're hard-blocked in the picker because they route to paid-only return methods in practice.
status (response)Outcome code. Values: RETURN_CREATED (success, RMA + label issued, status flipped to refunded, both webhooks fired), RETURN_ALREADY_INITIATED (the row already had an RMA, returning existing data with no Amazon round-trip), RETURN_IN_PROGRESS (another return is mid-flow), RETURN_NO_FREE_METHOD (Amazon offered only paid methods and paid_opt_in was false), ITEM_NOT_RETURNABLE (past return window or non-returnable item type), RETURN_REASON_REJECTED (none of our preferred reasons accepted), RETURN_SESSION_INVALID (cookies + ForceFreshLogin both rejected), RETURN_HTTP_ERROR (transport / non-2xx on any step — credits refunded).
rma_code (response)Amazon's return merchandise authorization code. Stored on the row as return_rma — used as the idempotency key for subsequent return calls on the same order.
return_method (response)Human-readable name of the chosen method (e.g. "UPS Drop Off", "Staples Drop Off").
return_method_id (response)Amazon's internal key for the chosen method (e.g. "UPSDropOff", "Staples_AMZL_PackageFree_Dropoff", "UPSLabel"). Stable across Amazon DOM changes — branch on this rather than return_method.
label_url (response)Raw Amazon URL for the printable return label prep page. Requires the Amazon account session to fetch — will 401 when accessed from a third-party server. Treat as fallback only.
label_mirror_url (response)OA's R2-mirrored copy of the label, served from our CDN with no auth required. This is the URL you should download from. Always a PDF — we wrap GIF/PNG/JPEG labels into a single-page PDF before upload so the URL is always .../label.pdf with Content-Type: application/pdf, no caller-side format branching needed. Empty when the label fetch from Amazon failed OR when the chosen method doesn't use a paper label (some QR-only dropoffs). Stable per-order key — re-issuing a return overwrites in place.
qr_code_url (response)Direct link to the QR code image for dropoff-flow returns (UPS Store / Staples / Amazon Hub Counter all support QR-only — no label needed). Empty when the chosen method doesn't offer a QR option.
dropoff_instructions (response)Human-readable instructions Amazon includes on the confirmation page (e.g. "Drop your package off at any UPS Store with the QR code or printed label."). Surface to the customer verbatim.
is_free (response)True when the chosen method has no fee. When false, fee_amount carries the cost.
fee_amount (response)Locale-formatted fee string (e.g. "$5.99") when is_free=false. Empty when free.
expiration_date (response)Date or locale-formatted string ("Jul 15, 2026") of when the return authorization expires (typically 30 days from issuance). Drop off before this date or the RMA becomes invalid.
asin (response)ASIN of the item this response's top-level fields describe. On single-item orders this is the only item. On multi-item orders this is the first successful item — use items[] for the full breakdown.
items (response)Per-item breakdown. Always length ≥ 1 on a successful or partial-success response. Single-item orders see a 1-entry array that mirrors the top-level fields; multi-item orders see one entry per processed item. Each entry carries: asin, status, rma_code, return_method, return_method_id, label_url, label_mirror_url (always .pdf — see above), qr_code_url, dropoff_instructions, is_free, fee_amount, expiration_date, message.
credits_charged (response)Total credits deducted for this call. One CREDIT_COST_RETURN charge per RETURN_CREATED item in items[]. Zero when no items succeeded (credits auto-refunded for transient failures).
available_methods (response)Full list of methods Amazon offered, formatted as "<internalKey>: <fee>" (e.g. "UPSDropOff: free", "Paid_UPSPickup: $5.99"). Always populated — useful for debugging RETURN_NO_FREE_METHOD outcomes and seeing which dropoff options exist for this ship-to ZIP.
failure_html (response)Raw HTML body Amazon returned at the failing step (only populated on non-RETURN_CREATED outcomes). Used internally for forensic refinement when Amazon changes their DOM; you can ignore.
POST/v1/account/orders/scrape

Scrape Account Order History

Walks an Amazon account's /your-orders pages backward in time until reaching since_date and returns every order card as JSON — order ID, date, total, ship-to name, status, line items (ASIN + title + quantity). Pure-read scraper; does NOT modify any order state. Pairs with POST /v1/order/import for customers backfilling historical orders into OrderAtlas.

Costs CREDIT_COST_SCRAPE (default 2) credits per call — flat rate, same as placing one order, regardless of how many orders come back. Zero-result scrapes (no orders matched the cutoff) skip the charge entirely. Charge applies even if the scrape is partial.

Required login fields (all 4): same contract as the return endpoint — account_email, account_password, account_2fa, account_proxy on every call. The system can't look up stored credentials here because the account may not yet have any rows in OrderAtlas (that's the whole point of scrape-then-import). Missing any field returns 400 MISSING_LOGIN_FIELDS.

Optional cookie_string: when supplied, skips the login step and drives the scrape against those cookies. Caller is responsible for cookie freshness — if expired, the scrape errors with SCRAPE_FLOW_ERROR.

Region: defaults to us (amazon.com). Pass region: "uk" for amazon.co.uk, etc. Same region semantics as the other endpoints.

How it works: Amazon paginates /your-orders/orders via ?timeFilter=year-YYYY&startIndex=N. We walk year filters from the current year backward (year-2026, year-2025, ...), 10 orders per startIndex page, until a page's oldest card is at or before since_date. Amazon now encrypts most order-card content client-side via their SiegeClientSideDecryption library, but they ship a non-JS fallback (?disableCsd=missing-library) that serves unencrypted HTML — we use that so our scraper can read the cards directly without a headless browser.

Pagination hint: the response's oldest_order_date is the YYYY-MM-DD of the oldest order in the result. If you want to continue past your cutoff, fire a second scrape with since_date set to a date before that value (and ideally a tighter cutoff to avoid re-scraping the same window).

max_orders cap: optional safety cap to bound the response size. 0 = unlimited (subject to since_date). Useful for sanity-testing the endpoint without scraping a whole multi-year history.

What the order data includes: for each order — platform order ID, order date, total (raw currency string e.g. "$36.05"), ship-to name, status (Amazon's prose lowercased — "delivered", "arriving tomorrow", "cancelled", etc.), and the line items (ASIN, title, quantity per item). Ship-to address fields beyond name are not always rendered on the order-list page; for full address data, import the order and re-fetch via GET /v1/order/:id.

Request
curl -X POST https://api.orderatlas.net/v1/account/orders/scrape \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "amazonPassword!",
    "account_2fa": "JBSW Y3DP EHPK 3PXP",
    "account_proxy": "http://user:pass@host:port",
    "since_date": "2026-05-01",
    "max_orders": 50
  }'
Response
{
  "account_email": "[email protected]",
  "count": 12,
  "credits_charged": 2,
  "credits_remaining": 491,
  "oldest_order_date": "2026-05-03",
  "pages_walked": 2,
  "orders": [
    {
      "platform_order_id": "112-4697981-6155432",
      "order_date": "2026-06-19",
      "total": "$36.05",
      "ship_to_name": "Jaden Hairston",
      "status": "arriving friday",
      "items": [
        {
          "asin": "B0BG9WZYTG",
          "title": "2-Piece Sakura Car Decal Set for Side Doors...",
          "quantity": 1
        }
      ]
    },
    {
      "platform_order_id": "113-6777116-2413048",
      "order_date": "2026-06-19",
      "total": "$24.69",
      "ship_to_name": "Cassandra Yohe",
      "status": "arriving tomorrow",
      "items": [
        {
          "asin": "B0DPKPFXBC",
          "title": "5 Pcs 52'' Ceiling Fan Blades Replacement...",
          "quantity": 1
        }
      ]
    }
  ]
}

Request Fields

account_email*Amazon account email to scrape. Required.
account_password*Amazon account password. Required on every call so the system can log in when no cached session is provided. Same contract as the return endpoint.
account_2fa*TOTP seed (base32, spaces OK). Required on every call. If the account doesn't use 2FA the field must still be present (any value).
account_proxy*Residential proxy URL or host:port:user:pass spec. Required on every call.
since_date*YYYY-MM-DD cutoff. Scraper stops once a page's oldest order is at or before this date. Required.
cookie_stringOptional pre-warmed Amazon session cookies. When supplied, skips the login step entirely. If the supplied cookies are stale, the scrape errors out — caller is responsible for freshness.
regionAmazon region. Defaults to us. Other supported: uk, ca, etc. — same set as the other endpoints.
max_ordersOptional safety cap to bound the response. 0 (default) = unlimited (subject to since_date). Useful for sanity-testing without scraping years of history.
orders (response)Array of scraped orders. Each carries platform_order_id, order_date, total, ship_to_name, status, and an items array (ASIN + title + quantity per line). Empty array when nothing matched.
count (response)Length of the orders array. Matches orders.length — included for callers that prefer not to compute it client-side.
pages_walked (response)Number of /your-orders pages the scraper hit before stopping. Useful for billing reconciliation and debugging pagination-stuck loops.
oldest_order_date (response)YYYY-MM-DD of the oldest order in the response. Empty when no orders matched. Pass a date before this value as since_date to continue past your cutoff in a follow-up call.
credits_charged (response)Credits actually deducted for this call. 0 when the scrape returned no orders (zero-result calls aren't charged); otherwise the flat CREDIT_COST_SCRAPE rate.
credits_remaining (response)Account credit balance after the deduction.
error codesPossible failure codes: MISSING_LOGIN_FIELDS (400 — one of the 4 required fields was blank), MISSING_SINCE_DATE / INVALID_SINCE_DATE (400 — date missing or not YYYY-MM-DD), AMAZON_LOGIN_FAILED (502 — login service couldn't auth with the supplied credentials), SCRAPE_FLOW_ERROR (502 — Amazon refused the scrape mid-flow; cookies may be stale or account flagged), SCRAPE_UPSTREAM_ERROR (502 — checkout service unreachable), INSUFFICIENT_CREDITS (402 — not enough credits for the flat rate).
POST/v1/account/order/scrape

Scrape Order Detail (per-ID)

Fetches full per-order detail (ship-to address, items with per-line prices + image + seller, totals breakdown, payment last-4, tracking, invoice URL) for one or more Amazon order IDs. Pairs with POST /v1/account/orders/scrape for an import workflow: bulk-scrape returns just IDs (cheap, single Amazon page hit); this endpoint returns the FULL detail per ID that your import flow needs.

Multi-ID: pass amazon_order_ids: ["112-...", "113-..."] as an array. Up to 50 IDs per call. Each ID drives one Amazon-side HTTP fetch through the same session (serialized to avoid tripping Amazon's rate-limiter on one account). Failed IDs land in the response's orders[] with their error populated — successful IDs are billed; failures are not.

Invoice mirror: for each successful detail fetch we ALSO download Amazon's printable invoice page (/gp/css/summary/print.html — requires the account session) and upload to OA's R2 CDN at https://cdn.orderatlas.net/invoices/<orderID>.html. Auto-deletes after 7 days via bucket lifecycle. Customers can fetch the mirror URL with no auth; print or save to PDF browser-side.

Cost: CREDIT_COST_SCRAPE_DETAIL (default 1) credit per ID that returned successfully — not per call. 10 IDs in one call → 10 credits if all succeed, 7 if 3 failed. Pre-flight balance check guards the peak so you see 402 up front instead of half-billing.

Required login fields (all 4): same contract as the other account endpoints — account_email, account_password, account_2fa, account_proxy. Missing any → 400 MISSING_LOGIN_FIELDS.

Webhook: fires account.order_detail.scraped once per call (not once per ID) carrying the full orders[] summary with each successful ID's invoice mirror URL.

Request
curl -X POST https://api.orderatlas.net/v1/account/order/scrape \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_email": "[email protected]",
    "account_password": "amazonPassword!",
    "account_2fa": "JBSW Y3DP EHPK 3PXP",
    "account_proxy": "host:port:user:pass",
    "amazon_order_ids": ["112-1234567-8901234", "113-9876543-2109876"]
  }'
Response
{
  "account_email": "[email protected]",
  "count": 2,
  "credits_charged": 2,
  "credits_remaining": 998,
  "orders": [
    {
      "platform_order_id": "112-1234567-8901234",
      "order_date": "2026-06-15",
      "status": "delivered",
      "subtotal": "$71.98",
      "shipping": "$0.00",
      "tax": "$5.78",
      "total": "$77.76",
      "ship_to": {
        "name":  "Barbara Krusniak",
        "phone": "5125551234",
        "line1": "123 Main St",
        "line2": "Apt 4B",
        "city":  "Austin",
        "state": "TX",
        "postal_code": "78701",
        "country": "US"
      },
      "items": [
        {
          "asin":  "B0C53SQYQY",
          "title": "Meythway 40-Piece Vintage Carved Gold Silverware Set...",
          "quantity": 1,
          "unit_price": "$35.99",
          "image_url": "https://m.media-amazon.com/images/I/81vwswtFkdL._SS284_.jpg",
          "seller": "Xuan Ya",
          "condition": "New"
        }
      ],
      "payment_last4": "2766",
      "payment_type":  "Mastercard",
      "tracking_number": "1Z999AA10123456784",
      "carrier": "UPS",
      "invoice_url":        "https://www.amazon.com/gp/css/summary/print.html?orderID=112-...",
      "invoice_mirror_url": "https://cdn.orderatlas.net/invoices/112-1234567-8901234.html",
      "order_detail_url":   "https://www.amazon.com/gp/your-account/order-details?orderID=112-..."
    },
    {
      "platform_order_id": "113-9876543-2109876",
      "error": "Amazon refused the order detail scrape. The order id may not exist on this account, or the session was rejected."
    }
  ]
}

Request Fields

account_email*Amazon account email. Required on every call.
account_password*Amazon account password. Required on every call.
account_2fa*TOTP seed (base32, spaces OK). Required on every call.
account_proxy*Residential proxy. Required on every call.
amazon_order_ids*Array of Amazon order IDs to fetch (max 50 per call). ["112-1234567-8901234", "113-9876543-2109876"]. The back-compat singular amazon_order_id is also accepted for single-ID callers.
cookie_stringOptional pre-warmed cookies. When supplied skips the login step.
regionAmazon region. Defaults to us.
orders (response)Per-ID outcome array. Same length + order as amazon_order_ids. Successful entries carry the full detail; failed entries carry error.
invoice_mirror_url (response)OA-mirrored CDN URL for the printable invoice page. Public, no auth needed — opens in any browser, print or save as PDF browser-side. Auto-deletes after 7 days — download / archive within that window if you need long-term retention. Empty when the invoice fetch failed.
ship_to (response)Recipient address block: name, phone, line1, line2, city, state, postal_code, country. All fields best-effort — Amazon's ship-track page format varies; missing/blank fields indicate that field wasn't rendered, not a parse failure.
items (response)Per-line items. Each entry has asin, title, quantity, unit_price, image_url, seller ("Sold by: X"), condition ("New" / "Used - Like New" / etc.).
credits_charged (response)Total credits deducted — CREDIT_COST_SCRAPE_DETAIL per successful ID. Failed IDs aren't charged. 0 when nothing succeeded.
error codesTop-level failures: MISSING_LOGIN_FIELDS, MISSING_ORDER_IDS (no IDs in array), TOO_MANY_ORDER_IDS (>50), AMAZON_LOGIN_FAILED, INSUFFICIENT_CREDITS (pre-flight, not enough for peak charge), SCRAPE_UPSTREAM_ERROR. Per-ID failures land inside orders[].error instead of failing the whole call.
POST/v1/order/:order_id/delivered

Mark Delivered

Manually mark an order as delivered. Optionally include a delivery photo URL.

Request
curl -X POST https://api.orderatlas.net/v1/order/112-4567890-1234567/delivered \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{ "delivery_photo_url": "https://..." }'
Response
{
  "success": true,
  "order_id": "112-4567890-1234567",
  "status": "delivered",
  "message": "Order marked as delivered"
}
POST/v1/order/:order_id/cancelled

Mark Cancelled

Manually mark an order as cancelled or lost.

Request
curl -X POST https://api.orderatlas.net/v1/order/112-4567890-1234567/cancelled \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{ "reason": "cancelled" }'
Response
{
  "success": true,
  "order_id": "112-4567890-1234567",
  "status": "cancelled",
  "message": "Order marked as cancelled"
}
GET/v1/order/:order_id

Get Order Details

Retrieve full details for a single order including the pricing breakdown, tracking info, and Aquiline conversion.

Request
curl https://api.orderatlas.net/v1/order/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "platform": "AmazonUS",
  "status": "confirmed",
  "platform_order_id": "112-4567890-1234567",
  "asin": "B08125WZHR",
  "product_title": "Example Product Name",
  "price": "$14.85",
  "shipping": "$0.00",
  "credit": "$14.85",
  "tax": "$0.00",
  "total": "$0.00",
  "price_amount": 14.85,
  "shipping_amount": 0,
  "credit_amount": 14.85,
  "tax_amount": 0,
  "total_amount": 0,
  "invoice_link": "https://cdn.orderatlas.net/invoices/112-4567890-1234567-3f8a9c1b2d4e5f60.html",
  "invoice_fetched_at": "2026-04-18T21:14:02Z",
  "logged_in": true,
  "auto_subscribe_save": true,
  "subscription_canceled": true,
  "is_prime": true,
  "tracking_number": "TBA123456789",
  "carrier": "AMZN",
  "aqualine_tracking": "AQUAA4223090426YQ",
  "order_html_url": "https://cdn.orderatlas.net/tracking/112-4567890-1234567.html",
  "order_html_updated_at": "2026-04-18T21:13:41Z",
  "failure_reason_code": "",
  "created_at": "2026-04-17T18:42:57Z",
  "updated_at": "2026-04-17T18:43:22Z",
  "credits_remaining": 998
}

Request Fields

priceItem(s) subtotal as the locale-formatted string Amazon rendered it (e.g. "$14.85") — product price × quantity, before shipping/tax/credits. For arithmetic use price_amount below.
shippingShipping & handling charges as the locale-formatted string. For arithmetic use shipping_amount.
creditAmazon courtesy credit, promotional credit, or gift card applied, as the locale-formatted string. Reduces the grand total. If non-zero and total=$0.00, the order was free to the buyer. For arithmetic use credit_amount.
taxSales tax collected as the locale-formatted string. For arithmetic use tax_amount.
totalGrand total — what the buyer actually paid after all credits and taxes — as the locale-formatted string. This is the amount charged to the payment method. For arithmetic use total_amount.
price_amountNumeric mirror of price with the currency symbol stripped. JSON number, not a string. 0 when the underlying string is empty or unparseable. Use this when you need to sum, compare, or format prices yourself — no parseFloat("$14.85") dance.
shipping_amountNumeric mirror of shipping.
credit_amountNumeric mirror of credit.
tax_amountNumeric mirror of tax.
total_amountNumeric mirror of total.
invoice_linkHosted copy of Amazon's order print summary (HTML), uploaded to our CDN once the order is confirmed. Empty until OrderAtlas successfully fetches the invoice from Amazon. Files expire after 14 days; the parsed amounts above (price/shipping/credit/tax/total) persist forever.
invoice_fetched_atUTC timestamp when the invoice HTML was scraped from Amazon and uploaded. Empty if the invoice hasn't been captured yet.
logged_inWhether the seller account session was successfully validated for this order. Use to split status=failed rows into login problems (logged_in=false) vs checkout-side problems (logged_in=true: price guard rejected, item out of stock, place-order blocked, etc.). Once true, never reverts to false on this row.
auto_subscribe_saveMirrors the auto_subscribe_save opt-in passed at order placement. When true, the order was placed via Amazon's Subscribe & Save flow to capture the (typically 5%) subscription discount.
subscription_canceledOnly meaningful when auto_subscribe_save=true. true = the post-place subscription cancel succeeded and no future shipments are scheduled. false = the cancel call did not confirm; the customer may still receive a follow-up delivery and should manually cancel via Amazon's "Your Subscribe & Save items" page. The first delivery (the actual order) is unaffected either way.
is_primePrime membership state we detected on the Amazon account during the offer-listing fetch. true = Prime member (free shipping eligible). false = NOT a Prime member (Amazon will add a shipping fee, often $5–10, to most orders). Field is OMITTED entirely (not false) when we couldn't determine membership — typically because the checkout failed at login before we ever loaded a product page. When this comes back as false AND the order failed with PRICE_EXCEEDED, the rejected cart total almost certainly includes a shipping fee the customer's price_max didn't budget for; raise price_max by the expected shipping or rotate to a Prime account. Stamped on the row as soon as we have a product page body, so it appears on diagnostic-failure rows too, not just on successful orders.
order_html_urlLive link to the raw order tracking-page HTML on our CDN. Stable per order — refreshes overwrite the same key. Refreshed every ~8h while the order is in flight; deleted 7 days after the order is marked delivered.
order_html_updated_atUTC timestamp of the most recent refresh of order_html_url. Use this to decide whether to re-poll for fresher data.
failure_reason_codeMachine-readable taxonomy code for why the order failed — empty for successful orders. Branch on this in your connector to drive retry / refund / manual-fulfillment logic. Stable across releases (the human-readable notes may rewrite as we improve diagnostics, the codes won't). Values include SHIPPING_RESTRICTED (destination won't accept this item — terminal), PRICE_EXCEEDED (Amazon price > your price_max), ACCOUNT_SOFTBLOCK (Amazon flagged the account at place-order even after a fresh login retry — manual amazon.com login required), ACCOUNT_QUEUE_FULL (per-account FIFO queue already has the max 50 orders waiting — slow down submissions on this account_email and resubmit when the queue drains), ACCOUNT_QUEUE_TIMEOUT (this order sat in the per-account queue for the 2-hour bounded wait without reaching the head — usually means an upstream order on the same account is wedged; investigate the in-flight orders and re-submit), DIGITAL_PRODUCT_UNSUPPORTED (Kindle/Audible/etc. — terminal), ASIN_SWAPPED (Amazon redirected the ASIN to a variant — terminal), SESSION_FLAGGED_AT_PLACE / SESSION_FLAGGED_NO_CSRF / OOS_AFTER_RETRIES / BOT_CHALLENGE_RX_SHELL (the gateway already retries these on a fresh login internally; if you see one of these as the final code, the retry also failed — usually a transient Amazon-side flag, retry the whole order later), CHECKOUT_BLOCKED (Amazon refused at SPC with no specific reason — usually transient), ADDRESS_VALIDATION_FAILED (address change didn't bind), ADDRESS_EXPORT_CONTROL_BLOCKED (Amazon refused the ship-to: ASIN has export controls on file and the destination is on Amazon's freight-forwarder list — terminal for this address; rotate the ship-to or the ASIN), PAYMENT_METHOD_BINDING_FAILED (added card didn't bind to the cart), ORDER_NOT_PLACED (placement appeared to succeed but couldn't verify via /your-orders), LOGIN_FAILED, CHECKOUT_RPC_ERROR, STALE_PLATFORM_ORDER_ID, GATEWAY_RESTART_INTERRUPTED, PAYMENT_CARD_NOT_FOUND (only emitted when payment_method.mode="find" — no on-account card matched the supplied last4 + expiration; customer needs to add the card to amazon.com first or switch to mode="add"), PAYMENT_CARD_EXPIRED (only emitted in find mode — card matched by last4 but Amazon marks it expired; customer needs to renew or replace it on amazon.com), PAYMENT_CARD_AMBIGUOUS (only emitted in find mode — 2+ saved cards share the same last4 + expiration; retry with payment_method.issuer_hint), INVALID_REQUEST (validation failed before checkout started — bad payment_method.mode value, missing last4, missing expiration when not a store card, etc; the request will never succeed without changing the payload). Login-stage codes (emitted instead of the generic LOGIN_FAILED when the bot can classify Amazon's rejection): PASSWORD_RESET_REQUIRED (Amazon redirected to /ap/forgotpassword/reverification — the account owner MUST sign into amazon.com manually, receive the emailed OTP, set a new password, and re-upload the new credentials to OrderAtlas; no retry on our side recovers this), LOGIN_CVF_REQUIRED (Amazon redirected to /ap/cvf/ or /ap/mfa/ — needs a one-time code emailed/SMSed to the account owner; OA can't solve this without the OTP), LOGIN_CAPTCHA_REQUIRED (Amazon redirected to a captcha; usually transient — wait a few minutes and retry, or rotate proxy), ACCOUNT_LOCKED (Amazon has TEMPORARILY locked the account after a burst of failed sign-ins or risk-engine triggers — page literally says "Your account has been locked" / "account is temporarily locked" / "too many failed sign-in attempts". Distinct from ACCOUNT_FLAGGED: locks are short cooldowns that auto-clear in ~24h after the account owner manually signs in to amazon.com from their normal device, no credential change required. Action: notify the customer to sign in manually once, wait ~24h, then resume — do NOT keep retrying programmatically, retries restart the cooldown), ACCOUNT_FLAGGED (Amazon flagged the account on /ap/cnep or showed "account has been suspended"; longer-term flag that needs device-trust rebuild — account owner needs to sign in from their normal device to clear), LOGIN_REDIRECT_LOOP (Amazon kicked us back to /ap/signin — usually means the claim was rejected; user may need to try fresh credentials).
DELETE/v1/order/:order_id

Delete Order

Permanently delete an order from your account. This cannot be undone.

Request
curl -X DELETE https://api.orderatlas.net/v1/order/550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "status": "deleted"
}
POST/v1/sessions/validate

Validate Session

Check if stored session cookies for a platform account are still valid.

Request
curl -X POST https://api.orderatlas.net/v1/sessions/validate \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "cookie_string": "session-id=...",
    "proxy": "ip:port:user:pass"
  }'
Response
{
  "valid": true,
  "status": "LOGGED_IN",
  "cookie_string": "session-id=...",
  "message": "Session is active"
}
GET/v1/settings

Get Settings

Get your account-level settings including webhook URLs, Discord webhook, tracking-conversion preferences, ship-from address, current credit balance, and your webhook signing secret.

Request
curl https://api.orderatlas.net/v1/settings \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "convert_tracking": true,
  "webhook_url": "https://mysite.com/webhook",
  "discord_webhook_url": "https://discord.com/api/webhooks/...",
  "webhook_secret": "a1b2c3...64-char-hex",
  "rate_limit": 60,
  "credit_balance": 4500,
  "email": "[email protected]",
  "ship_from_name": "Your Store",
  "ship_from_line1": "123 Main St",
  "ship_from_city": "Austin",
  "ship_from_state": "TX",
  "ship_from_zip": "78701",
  "ship_from_phone": "5125551234",
  "ship_from_required": false
}
  • webhook_secret is the HMAC-SHA256 key for verifying outbound webhook deliveries. Every webhook we send carries X-OrderAtlas-Signature: sha256=<hex> — your handler should compute HMAC-SHA256(raw_body, webhook_secret) and reject mismatches. Auto-generated 64-char hex by default; user-set values may differ in length and charset.
  • ship_from_required is true when convert_tracking is on but no ship-from address is configured. While true, instant-tracking labels fall back to a platform-provided default — set your own values to override.
  • Account-wide settings here apply to every API key on the account. To set per-key webhook overrides, use PATCH /v1/api-keys/:key_id.
PUT/v1/settings

Update Settings (account-level)

Update account-level settings. Webhook URLs set here apply to all API keys on the account. To override the webhook for a specific key, use PATCH /v1/api-keys/:key_id. Supports partial updates — only send the fields you want to change. Discord webhook URL must be a valid discord.com/api/webhooks/ URL.

Request
curl -X PUT https://api.orderatlas.net/v1/settings \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "convert_tracking": true,
    "webhook_url": "https://mysite.com/webhook",
    "discord_webhook_url": "https://discord.com/api/webhooks/123/abc"
  }'
Response
{
  "convert_tracking": true,
  "webhook_url": "https://mysite.com/webhook",
  "discord_webhook_url": "https://discord.com/api/webhooks/123/abc",
  "rate_limit": 60
}
POST/v1/settings/rotate-webhook-secret

Rotate / Set Webhook Secret

Either generate a new HMAC-SHA256 signing secret for outbound webhook deliveries, OR set a custom value (e.g. one you already use in another system). The previous secret stops being valid immediately — update your webhook handler with the new secret first.

Request
# Auto-generate (no body):
curl -X POST https://api.orderatlas.net/v1/settings/rotate-webhook-secret \
  -H "X-API-Key: oatl_live_abc123def456..."

# Set a custom value:
curl -X POST https://api.orderatlas.net/v1/settings/rotate-webhook-secret \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{"webhook_secret": "my-custom-32-char-or-longer-secret"}'
Response
{
  "webhook_secret": "a1b2c3...64hex"
}
  • Every outbound webhook delivery includes a X-OrderAtlas-Signature: sha256=<hex> header. The hex is HMAC-SHA256(raw_body_bytes, webhook_secret).
  • Custom secret rules: 16–256 chars, printable ASCII only (no whitespace, no control chars). Long random strings are best for security; short or guessable values weaken HMAC.
  • Always verify with a constant-time comparison and reject requests that don't match. Anyone who learns your webhook URL can otherwise replay the JSON shape.
  • Verification examples — Node: crypto.createHmac("sha256", secret).update(body).digest("hex"). Python: hmac.new(secret.encode(), body, hashlib.sha256).hexdigest(). Compare against the hex part of the header (after sha256=).
  • Read your current secret from GET /v1/settings (field webhook_secret) or copy from the dashboard.
PATCH/v1/api-keys/:key_id

Update API Key (per-key webhook override)

Set per-key webhook overrides. The values you set here only apply to orders placed with this specific API key, and take precedence over the account-level webhook from PUT /v1/settings. Pass an empty string ("") to clear an override and fall back to the account default. Supports partial updates — only send the fields you want to change.

Request
curl -X PATCH https://api.orderatlas.net/v1/api-keys/123e4567-e89b-12d3-a456-426614174000 \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "webhook_url": "https://prod.mysite.com/webhook",
    "discord_webhook_url": "https://discord.com/api/webhooks/123/abc",
    "name": "production"
  }'
Response
{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "key_prefix": "oatl_live_a",
  "name": "production",
  "email": "[email protected]",
  "active": true,
  "created_at": "2026-04-01T00:00:00Z",
  "webhook_url": "https://prod.mysite.com/webhook",
  "discord_webhook_url": "https://discord.com/api/webhooks/123/abc"
}
  • Webhook resolution order at delivery time: per-order webhook (set on POST /v1/order) → per-key override → account-level default → none.
  • Set webhook_url or discord_webhook_url to "" (empty string) to clear the override and fall through to the account default.
  • A key may only be edited by the user who owns it (matched by email on the authenticated session).
POST/v1/billing/topup

Add Credits

Create a Stripe Checkout session to purchase credits. Redirect the user to the returned URL to complete payment.

Request
curl -X POST https://api.orderatlas.net/v1/billing/topup \
  -H "X-API-Key: oatl_live_abc123def456..." \
  -H "Content-Type: application/json" \
  -d '{
    "credits": 100,
    "success_url": "https://orderatlas.net/dashboard",
    "cancel_url": "https://orderatlas.net/dashboard"
  }'
Response
{
  "checkout_url": "https://checkout.stripe.com/c/pay/cs_live_...",
  "credits": 100
}
GET/v1/billing/balance

Get Credit Balance

Returns the current credit balance and the cost of each action.

Request
curl https://api.orderatlas.net/v1/billing/balance \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "credit_balance": 4500,
  "costs": {
    "order_placement": 2,
    "tracking_conversion": 2,
    "order_import": 1
  }
}
GET/v1/billing/transactions

List Transactions

Get the credit transaction history for your account.

Request
curl https://api.orderatlas.net/v1/billing/transactions \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "transactions": [
    {
      "id": "uuid",
      "amount": 100,
      "type": "deposit",
      "description": "Purchased 100 credits",
      "created_at": "2026-03-20T10:00:00Z"
    },
    {
      "id": "uuid",
      "amount": -1,
      "type": "order_debit",
      "description": "Order placement: 112-456...",
      "created_at": "2026-03-25T14:30:00Z"
    }
  ]
}
GET/v1/dashboard/overview

Dashboard Overview

Summary stats: order counts, credit balance, API usage, and recent orders.

Request
curl https://api.orderatlas.net/v1/dashboard/overview \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "orders_today": 12,
  "active_tracking": 5,
  "total_orders": 347,
  "credit_balance": 4500,
  "api_calls_month": 2841
}
GET/v1/dashboard/orders

List All Orders

Paginated list of all orders. Supports limit + offset (max limit 200) and optional search + status filters. search is a case-insensitive substring match against amazon_order_id, external_order_id, product_title, asin, and account_email. status is an exact match against the order status column (one of pending, processing, confirmed, shipped, delivered, failed, etc.). When either filter is set, total in the response reflects the count under that filter — not the global total — so you can drive pagination directly off it.

Request
curl "https://api.orderatlas.net/v1/dashboard/orders?limit=20&offset=0&search=B0CWNJ&status=failed" \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "orders": [
    {
      "id": "uuid",
      "platform_order_id": "112-4567890-1234567",
      "platform": "AmazonUS",
      "status": "delivered",
      "total": "$29.99",
      "tracking_number": "1Z999AA10123456784",
      "created_at": "2026-03-25T14:30:00Z"
    }
  ],
  "total": 347
}
GET/v1/dashboard/tracking-conversions

Tracking Conversions

List orders with converted tracking numbers.

Request
curl https://api.orderatlas.net/v1/dashboard/tracking-conversions \
  -H "X-API-Key: oatl_live_abc123def456..."
Response
{
  "orders": [
    {
      "id": "uuid",
      "platform_order_id": "112-4567890-1234567",
      "status": "shipped",
      "tracking_number": "1Z999AA10123456784",
      "converted_tracking": "AQ9281736450",
      "created_at": "2026-03-25T14:30:00Z"
    }
  ],
  "total": 42
}

Error Responses

All errors return JSON with an error field:

Error Response Example
{
  "error": "insufficient credits: have 0, need 1"
}
400 Bad request / validation error
401 Invalid or missing API key / JWT
402 Insufficient credits
404 Resource not found
429 Rate limit exceeded (60 req/min default)
500 Internal server error
503 Service unavailable — see code field for the specific reason

Error Code Field

Some errors include a stable machine-readable code alongside the human-readable error. Branch on code, not on the error string — error wording can change without notice.

503 proxy_unreachable
{
  "error": "proxy 154.7.143.184:5804 unreachable: dial tcp 154.7.143.184:5804: connect: connection refused",
  "code": "proxy_unreachable"
}

Returned from POST /v1/order and POST /v1/order/import when the per-account proxy refuses or times out a TCP connection (3-second probe before checkout starts). Nothing is written to the database and no credits are charged — fix the proxy and retry. Common causes: provider outage on that IP, rotated/revoked proxy credentials, or the IP being removed from your pool. Probe runs against the host:port in the proxy field of your request.