API is live

Place Wonder orders
from your own app.

One key, one call to price a cart, one call to confirm it. Your accounts, your cards, your chains — nobody else can see or spend them.

Building with an AI?

Copy this and paste it into Claude, ChatGPT or Cursor. It contains everything needed to write a working integration.

Quickstart

What a real integration looks like end to end. This runs the actual shape of the calls, so you can watch the two steps happen.

Get your key from Discord with /api. It is sent by DM and shown once.

# 1. price a cart — nothing is charged yet
curl https://yonderdrop.com/v1/checkouts \
  -H "Authorization: Bearer yd_live_..." \
  -H "Content-Type: application/json" \
  -d '{"cart_code":"ABC123","address":"742 Example Ave, Springfield, IL"}'

# → nothing charged yet. This is what comes back:
{
  "ok": true,
  "id": "chk_8f2a",          // use this to confirm or cancel
  "status": "pending",
  "restaurant": "Bella Vista Kitchen",
  "items": [
    { "name": "Chicken Burrito Bowl", "quantity": 1, "subtotal": "$20.80" }
  ],
  "price": {
    "subtotal": 20.80,      // the food
    "discount": -15.00,     // applied for you
    "service": 2.99,
    "tax": 0.78,
    "tip": 0.00,
    "credit": -0.00,       // account credit spent on this order
    "total": 9.57          // what the card is charged
  },
  "account": "buyer-04@…",   // who it orders as
  "card": "•••• 4242",
  "expires_in": 300         // seconds before the hold is released
}

# 2. confirm it — this is the one that spends a checkout
curl https://yonderdrop.com/v1/checkouts/chk_.../confirm \
  -H "Authorization: Bearer yd_live_..." \
  -H "Idempotency-Key: your-own-id"

# → {"ok":true,"order_id":"...","tracking":"https://yonderdrop.com/track/X7K2M9"}
Nothing is charged until /confirm. A prepared checkout can be inspected or cancelled for free, and if anything fails before the order reaches Wonder you are never charged for it.

Authentication

Every request carries your key as a bearer token.

Authorization: Bearer yd_live_xxxxxxxxxxxxxxxxxxxxxxxx
Your key is stored only as a hash — we cannot read it back to you. Lost it? Run /api again; the old key stops working immediately. Anyone holding it can order on your plan and spend your cards.

How an order works

Two steps, on purpose. The first one prices the cart and costs nothing. The second is the only thing in the whole API that spends money. Between them you can change anything, or walk away.

POST /v1/checkouts                → pending    priced, held 15 min, FREE
       │
       ├── PATCH  /v1/checkouts/{id}          change tip, notes, card…  still free
       ├── POST   /v1/checkouts/{id}/name     rename the account, still free
       ├── POST   /v1/checkouts/{id}/reprice  ask again, renew the hold
       │
       ├── POST   /v1/checkouts/{id}/cancel   → cancelled  nothing charged, ever
       ├── (do nothing for 5 minutes)         → expired    nothing charged, ever
       │
       └── POST   /v1/checkouts/{id}/confirm  → placed     CHARGED. order is live
                    ↓
              GET /v1/orders/{id}             → delivered

Stopping an order before it goes through

A checkout is not an order. Pricing one charges nothing, reserves nothing on your card and spends nothing from your daily plan — it only holds a lane for you for fifteen minutes. There are two ways to back out, and both cost exactly nothing:

# say so, and the lane is freed immediately for your next order
curl -X POST https://yonderdrop.com/v1/checkouts/chk_8f2a/cancel \
  -H "Authorization: Bearer yd_live_..."

# → {"ok":true,"id":"chk_8f2a","status":"cancelled"}

Or do nothing at all. After fifteen minutes the hold lapses on its own, the lane goes back, and the checkout stops existing. You never have to clean up after yourself — cancelling is just faster, and gets your lane back for the next order sooner.

Once you call /confirm, that is a real order for real food. It cannot be cancelled through this API. If you need to stop it after that point, it is a conversation with the restaurant, not an API call. So: price freely, adjust freely, cancel freely — and treat confirm as the point of no return.

What "charged" actually means

Confirm does two separate things. It spends one checkout from your daily plan, and it puts a real order on a real card. If the order fails before it ever reaches the restaurant, your checkout is given straight back — you are not billed a slot for something that never happened.

If it fails after submission, the response says "outcome_known": false. That means the food may genuinely be on its way and we will not pretend otherwise, so nothing is refunded and you should check before trying again. Anything that reports "outcome_known": true provably never landed.

Every option

Only cart_code is ever required. Everything else has a sensible default, and anything you leave out falls back to your saved settings first, then to how the cart itself was built.

FieldTypeDefaultWhat it does
cart_codestringrequiredThe six-character code from the extension or bookmarklet.
addressstringfrom the cartWhere it goes. If the cart was grabbed with an address saved, this is optional.
address2stringfrom the cartThe apartment, suite, floor or unit — e.g. "Apt 4B". Also accepted as apt. It survives a later address change, so re-pointing an order does not send it to the building's front door.
tipnumber0Dollars for the driver, e.g. 3.50. Not cents.
notesstringnoneInstructions for the driver, e.g. "Ring the bell twice". Max 200 characters.
dropoffstringMEET_AT_DOORHow it is handed over: MEET_AT_DOOR, LEAVE_AT_DOOR or MEET_OUTSIDE.
fulfillmentstringfrom the cartDELIVERY or PICKUP. Pickup needs no address and no dropoff.
cardstringyour pool"number,mm/yy,cvv,zip". Used once for this order and never stored — nothing is added to your pool and nothing is kept afterwards.
chainstringbest-funded free oneWhich chain to order on, e.g. "1". Left out, the chain whose next account holds the most credit is used — Wonder takes that balance off the order, so it is the cheapest one available.
use_creditbooleanfalseScan every account and buy with whichever holds the most credit.
jigbooleantrueVaries the apartment/unit line so repeat orders to one address are not identical. On by default — send false to turn it off.

So a full order with a tip, driver notes and a hand-off preference is one call:

curl https://yonderdrop.com/v1/checkouts \
  -H "Authorization: Bearer yd_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "cart_code": "ABC123",
    "address": "742 Example Ave, Springfield, IL",
    "address2": "Apt 4B",
    "tip": 5.00,
    "notes": "Ring the bell twice, gate code 4417",
    "dropoff": "LEAVE_AT_DOOR"
  }'

Using one card for one order

Pass card and that order is paid with it — nothing goes into your pool, nothing is stored, and your pinned card is untouched. Useful when a customer's order should not come off your own card, or when you are testing.

-d '{
    "cart_code": "ABC123",
    "address": "742 Example Ave, Springfield, IL",
    "card": "4242424242424242,09/28,414,10001"
  }'

Leave it out and it draws from your pool instead — the pinned card first, then whichever is at the front. If a card is refused you get CARD_DECLINED before anything is ordered; swap it with PATCH on the same checkout rather than starting over.

You do not have to carry card numbers around to retry. Send {"card": "pool"} and the next unclaimed card in your pool is drawn for you, exactly as the Discord panel's "try another card" does. An exhausted pool answers NO_CARD, and says whether it is empty or simply all on other open checkouts.

curl -X PATCH https://yonderdrop.com/v1/checkouts/chk_8f2a41c07b93 \
  -H "Authorization: Bearer yd_live_..." \
  -d '{"card": "pool"}'

A fraud decline works the same way. The first FORTER_DECLINE on an account burns that card but keeps the checkout open, so a different card can be sent and confirmed again. If a second card is refused on the same account, the account itself is the problem: it is retired from the chain and the checkout closes, so build a new one on another chain.

Pickup is the same call with no address at all:

-d '{"cart_code": "ABC123", "fulfillment": "PICKUP", "tip": 2.00}'

Changing it after pricing

Priced it and want something different? PATCH the checkout before you confirm. Send only what changes — the rest stays as it was, and the checkout is re-priced and returned.

curl -X PATCH https://yonderdrop.com/v1/checkouts/chk_8f2a \
  -H "Authorization: Bearer yd_live_..." \
  -H "Content-Type: application/json" \
  -d '{"tip": 8.00, "notes": "Leave with the doorman"}'

# → the whole checkout back, plus what actually changed:
#   "changed": ["tip", "notes"], "price": { ... "total": 12.57 }
You can changeNotes
tipAny amount from 0 up.
notesSend "" to clear them.
dropoffSame three values as above.
fulfillmentSwitch between delivery and pickup.
cardSwap the card after a decline. Keeps your place in the queue — do not rebuild the checkout for this.
addressSomewhere else entirely. Re-prices against the new one.
Prefer setting these once instead of on every order? PATCH /v1/settings takes tip, dropoff, fulfillment, notes, jig and chain as your defaults. Anything sent with a checkout still wins — a default only fills a silence.

Endpoints

POST/v1/checkoutsprice a cart — nothing charged
GET/v1/checkouts/{id}inspect it
PATCH/v1/checkouts/{id}change tip, notes, dropoff, card, address
POST/v1/checkouts/{id}/namerename the account — driver app, receipt & tracker follow
POST/v1/checkouts/{id}/repriceask again and renew the hold
POST/v1/checkouts/{id}/confirmplace the order
POST/v1/checkouts/{id}/canceldrop it, free
GET/v1/ordersyour orders
GET/v1/orders/{id}one of them
GET/v1/track/{id}job, checkout or order id — all work
GET/v1/jobswhat is running
GET/v1/jobs/{id}one job's progress
POST/v1/accountsgenerate accounts into your pool — returns a job
GET/v1/accountsevery account and its credit
GET/v1/chainschains and where the credit sits
GET/v1/chains/queuecodes waiting to be paid
POST/v1/chains/{n}/lockclaim a chain
POST/v1/chains/{n}/unlockrelease it
GET/v1/cardsyour card pool, masked
POST/v1/cardsadd cards
POST/v1/cards/pinwhich card orders use next
DELETE/v1/cards/{last4}burn one
GET/v1/cards/burnswhat was burned, and why
DELETE/v1/cardsclear the pool
GET/v1/settingsyour defaults
PATCH/v1/settingschange them
GET/v1/planwhat is left today
GET/v1/mewho this key is
GET/v1/statuseverything, one call
GET/v1/subscriptionplan and what is buyable
POST/v1/subscription/checkouta crypto payment link
GET/v1/api-keys/currentwhen it was made and last used
POST/v1/api-keys/regeneraterotate your key
DELETE/v1/api-keys/currentrevoke it
GET/v1/healthis it up (no key needed)

Errors

Every failure has a stable code, a sentence written for a human, and a flag telling you whether trying again could help.

{
  "ok": false,
  "code": "CHECKOUT_FAILED",
  "error": "Bella Vista Kitchen is open but does not serve this address",
  "retryable": true
}
CodeHTTPMeansRetry?
ADDRESS_REJECTED400Wonder would not accept that addressyes
ADDRESS_REQUIRED400delivery asked for with no address anywhereno
ALREADY_PLACED409that order is on its way and cannot be changed or cancelledno
AMBIGUOUS_CARD400more than one of your cards ends in those fourno
API_UNREACHABLE502the ordering API is not answeringyes
BAD_ADDRESS400the address you sent was emptyno
BAD_CARD400the card could not be parsedno
BAD_CART400that cart could not be read — re-grab itre-grab
BAD_COUNT400count must be a whole number from 1 to 10no
BAD_DROPOFF400dropoff is one of the three named valuesno
BAD_FULFILLMENT400fulfillment is DELIVERY or PICKUPno
BAD_JSON400the body is not a JSON objectno
BAD_SETTING400that setting is the wrong type or not a valid valueno
BAD_TIER400tier is starter, pro or unlimitedno
BAD_TIP400tip is not a number of dollars, or is above the ceilingno
CARD_DECLINED400the card was refused while pricing. Nothing was chargedanother card
CARD_REJECTED400the swapped card was refusedanother card
CARD_REQUIRED400no card in the body of a cards callno
CART_REQUIRED400no cart_code was sentno
CHAIN_BUSY409that one chain is still settlingyes
CHAIN_IN_USE409an order is being placed on that chain right nowyes
CANNOT_PRICE400this cart cannot be ordered as configured — the message says what to change, e.g. pickup is not offered at that addressno, change something first
BAD_CHAIN400chain must be a string of letters and digits — leave it out entirely to generate into your poolno
CHAIN_STALLED409the rung above has not completedno
CHECKOUT_FAILED400pricing failed — read error for whichyes
CHECKOUT_UNAVAILABLE502the payment provider is not answeringyes
INTERNAL500our faultyes
IN_PROGRESS409something else is already using that checkoutyes
NOTHING_TO_CHANGE400the PATCH body changed nothingno
NOT_FOUND404nothing of yours has that idno
NO_CARD400no card sent and your pool is empty, or every card is on another open checkoutadd a card
FORTER_DECLINE400/502the fraud gate refused it — first time the checkout stays open for another card, second card retires the accountanother card, then another chain
CREDIT_NOT_SUPPORTED409a credit-funded order — the current checkout flow does not spend account credit on its own; order on a referral chain insteadno
NAME_UNSUPPORTED409rename on a checkout that is not on the mobile flow — the only surface with a name-change endpointno
NAME_FAILED400Wonder refused the renameyes
BAD_NAME400no name in the body — send {"name":"John Smith"}no
BAD_ADDRESS400could not resolve a delivery address with coordinatesno
NO_CHAIN_FOR_ACCOUNT400the account holding the most credit is on no chain, so there is no referral to order underno
ACCOUNT_NOT_ON_CHAIN400that account is not a rung on its own chainno
NO_CHAINS400you have no chains yetafter /v1/accounts
NO_CHAIN_READY409every chain is mid-order or settlingyes
NO_CREDIT_READ400credit could not be read on any accountyes
NO_KEY404you have no API keyno
NO_REFERRAL400no unused account below the buyer, so the order would credit nobodyafter /v1/accounts
NO_SESSION400that account has no saved session, so it cannot orderno
NO_SESSIONS400no accounts with a saved session to scan for creditno
NO_SUCH_CARD404no card of yours ends in those four digitsno
NO_SUCH_CHAIN404that chain is not one of yoursno
NO_SUCH_CHECKOUT404gone, expired after 5 minutes, or never yoursno
NO_SUCH_JOB404no job of yours with that id — they are kept an hourno
NO_SUCH_ORDER404no order of yours with that idno
NO_SUCH_ROUTE404no such endpoint at that method and pathno
PICKUP_HAS_NO_ADDRESS400this checkout is pickup — switch it to delivery firstno
QUOTA_EXHAUSTED402today's checkouts are spent; resets_at says whentomorrow
RATE_LIMITED429too many calls — wait retry_after secondsyes
REF_REQUIRED400/v1/track needs an id to look upno
TIMEOUT504we gave up waiting on something upstreamyes
TOO_MANY_HELD409five checkouts already openafter one closes
TOO_MANY_JOBS409three generation jobs already runningyes
UNAUTHORIZED401the key is missing, wrong or revokedno
UNKNOWN_SETTING400that is not a settingno
USE_CARD_PIN_ENDPOINT400pin cards with /v1/cards/pin, not settingsno
If a confirm fails, read outcome_known. When it is false the order may still have reached Wonder and the food may be on its way, so nothing is refunded and you should check before retrying. When it is true the order provably never landed and your checkout is already back.

Plans & limits

PlanPriceCheckouts / day
Free—2
Starter$256
Pro$5030
Unlimited$115no cap

A checkout is spent when an order is placed. Anything that fails, or that you cancel, costs nothing. The day resets at midnight.

Rate limits

Separate from the daily plan. The plan caps how many orders you place; these cap how fast you may call, and they are priced by what a call actually costs us. Every limit is counted per key.

BucketRateBurstCovers
Reads120 / min40plan, chains, cards, orders, jobs, tracking, settings
Writes40 / min12adding cards, settings, chain locks, cancels, adjusting a checkout
Pricing20 / min6POST /v1/checkouts and reprice — each one prices a real cart
Placing12 / min4/confirm — the expensive, irreversible one
Generating6 / min3POST /v1/accounts — each account costs an SMS

Burst is how many you may fire at once before the rate starts to matter — it refills continuously, so a steady caller never notices it.

A limited request answers 429 with a Retry-After header and the same figure in retry_after, in seconds:

{
  "ok": false,
  "code": "RATE_LIMITED",
  "error": "Too many read requests. Try again in 4s.",
  "retryable": true,
  "retry_after": 4.2
}

Every successful response also carries X-RateLimit-Remaining and X-RateLimit-Limit, so you can slow down before you are told to.

A request rejected for a bad argument gives its token back and is charged to the read bucket instead — fixing a typo four times should not cost you a generation slot.

Tracking

Every placed order returns a link you can hand straight to your customer. It shows the driver, the ETA, every item with its photo and the drop-off photo, and never shows a price or an address you did not give it. The page updates itself until the food lands.

https://yonderdrop.com/track/X7K2M9

Try it

Every endpoint, answering in the exact shape the real one answers in. The data below is invented — nobody's key, nobody's cart — but the fields, the error codes and the failure cases are the ones the server actually returns, so what you build against this works against the real thing.

yonderdrop.com/v1
sandbox
GET /v1/plan

nothing is charged, nothing is real
200 —

      
Simulated. Point the same requests at https://yonderdrop.com/v1 with your own key for the real ones.