ubereats.com logo
ubereats.com

place-food-order

Installation

Adds this website's skill for your agents

 

Summary

Assemble a food order on Uber Eats: set a delivery address, find a restaurant, open its menu, and add items to the cart up to the checkout-review boundary. Read-only — stops at the login/payment wall and never submits (places) the order.

FIG. 01
FIG. 02
FIG. 03
FIG. 04
FIG. 05
FIG. 06
FIG. 07
SKILL.md
212 lines

Place a Food Order on Uber Eats

Purpose

Drive the Uber Eats web app to assemble a food order for a given delivery address: set the address, find a specific restaurant, open its menu, add one or more items to the cart, and open the cart to read back the order summary (line items + subtotal). This skill is read-only up to — but not including — payment: it stops at the cart / "Go to checkout" boundary and never submits (places) the order, because checkout is gated behind an Uber login and placing an order charges a real payment method. Output is a structured summary of the assembled cart plus a flag indicating that checkout requires authentication.

When to Use

  • "Add a Big Mac from the nearest McDonald's to my Uber Eats cart for delivery to <address>."
  • Pricing/availability recon: what does item X cost for delivery to a given address, and is the store currently open for that daypart?
  • Pre-staging an order for a human to review and submit in the app (the skill hands back the store URL + cart contents).
  • Any flow that needs the cart total / fees for an address without actually paying.

Do not use this skill to complete a purchase — it deliberately cannot. A logged-in, payment-capable checkout is a separate, higher-risk skill.

Workflow

Recommended method: browser. There is no usable public/anonymous API — the ordering surface (feed, store, checkout) is served by internal Uber endpoints that require session cookies + an x-csrf-token header and sit behind Cloudflare and reCAPTCHA (see Gotchas). The rendered web app is the only reliable surface.

1. Stealth + residential-proxy session (mandatory)

SID=$(browse cloud sessions create --keep-alive --verified --proxies \
  | node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))")
export BROWSE_SESSION="$SID"

--verified and --proxies are both required. ubereats.com fronts every page with Cloudflare + Cloudflare WAF; a bare session risks a 301/challenge on the homepage.

2. Open the homepage and dismiss the cookie dialog

browse open "https://www.ubereats.com/" --remote
browse wait timeout 4000 --remote
# A cookie consent dialog usually renders. Click "Got it" if present.
browse snapshot --remote     # find: dialog > button: Got it

3. Set the delivery address (mandatory first action)

The homepage combobox: Enter delivery address gates everything. Type into it, wait for the autocomplete listbox, and click the first matching option:

# From the snapshot, click the address combobox ref, then type (do NOT press Enter yet)
browse click "<combobox-ref>" --remote
browse type "20 W 34th St, New York, NY 10001" --remote
browse wait timeout 2500 --remote            # options render ~2-3s after typing
browse snapshot --remote                     # listbox > option: "20 W 34th St New York, NY"
browse click "<first-option-ref>" --remote
browse wait timeout 5000 --remote

Selecting an option navigates to /feed?diningMode=DELIVERY&pl=<PAYLOAD>. Capture that pl value — it is a reusable, URL-safe base64 of a URI-encoded JSON location object:

{"address":"20 W 34th St","reference":"<uuid>","referenceType":"uber_places","latitude":40.74868,"longitude":-73.98561}

Once you have pl, you can deep-link straight into any subsequent page (feed / search / store) with the delivery address already applied — no need to re-enter it.

4. Find the restaurant

Either use the header combobox: Search Uber Eats, or deep-link directly:

browse open "https://www.ubereats.com/search?pl=<PL>&q=McDonald%27s&vertical=ALL&searchType=GLOBAL_SEARCH&sc=SEARCH_BAR" --remote
browse wait timeout 5000 --remote
browse snapshot --remote

Store results are link elements whose urlMap href is https://www.ubereats.com/store/<slug>/<storeId> (the <storeId> is an opaque URL-encoded token, e.g. VVyxLQxmQrK3Z9dlkZ0-bA). Pick the closest/most-relevant store and click it (or open the store URL directly, appending ?diningMode=DELIVERY).

5. Add a menu item to the cart

On the store page the menu items are links, each containing a nested button: Quick Add.

browse snapshot --remote
browse click "<quick-add-ref>" --remote      # opens the item customization DIALOG (not a direct add)
browse wait timeout 3000 --remote
browse snapshot --remote                      # dialog has option add-ons + button: "Add N to order"
browse click "<add-N-to-order-ref>" --remote  # this is the add-to-cart action
browse wait timeout 3000 --remote
  • "Quick Add" does not add directly — it always opens the item dialog. The actual add is the button: Add <N> to order inside the dialog.
  • If the item has required option groups (size, protein, etc.), select them in the dialog before "Add N to order" is enabled. Simple items add with defaults.
  • Confirm success: the header cart button label flips from button: 0 carts to button: 1 item (then 2 items, …).

6. Open the cart and read back the order (STOP HERE)

browse click "<cart-button-ref>" --remote     # the "N item(s)" header button
browse wait timeout 3000 --remote
browse snapshot --remote
browse screenshot --remote --path cart.png

Read the cart drawer: each listitem line item (name + price), the StaticText: Subtotal value, and the link: Go to checkout. Emit the structured summary. Do not click "Go to checkout" as part of fulfilling an order — it leaves the read-only zone.

7. (Optional) Confirm the checkout boundary — do not authenticate

Clicking link: Go to checkout while logged out redirects to https://auth.uber.com/v2/?...&redirect=%2Fcheckout — a phone/email + Google/Apple login wall. This confirms checkout requires auth and payment. Never enter credentials, never proceed past this wall, never place the order.

8. Release the session

browse cloud sessions update "$SID" --status REQUEST_RELEASE

Site-Specific Gotchas

  • READ-ONLY / NEVER PLACE THE ORDER. Placing an order charges a real payment method. Checkout is behind auth.uber.com anyway (verified: Go to checkoutauth.uber.com/v2/?...redirect=%2Fcheckout). The skill's terminal state is the cart drawer. Adding items to the cart does not require login; submitting the order does.
  • Cloudflare + Cloudflare WAF on every page. Pre-run probe returned HTTP 301 with cloudflare + cloudflare-waf. --verified --proxies is mandatory; the homepage loaded cleanly with both. A bare session is not reliable. There is also a Google reCAPTCHA notice on the homepage — it did not fire a visible challenge during testing with verified+proxies, but budget for it.
  • Address is a hard gate. Nothing (feed/search/store) works until a delivery address is set. The homepage combobox is the only way to establish one from scratch.
  • The pl param is the reusable location key. After the address is picked, every URL carries pl=<url-safe-base64>. Decode: decodeURIComponent(atob(decodeURIComponent(pl))) → the location JSON (address, reference (uber_places uuid), latitude, longitude). Reuse the same pl to deep-link /feed, /search?...&q=, and /store/<slug>/<id> with the address already applied — this is the fastest path and skips re-typing the address.
  • browse type needs a focused field + a wait for autocomplete. Click the address combobox first, then type (do not append Enter — that submits before the suggestion listbox renders). Wait ~2–3s for options, then click an option.
  • "Quick Add" is a misnomer — it opens the item dialog, it does not add. The real add-to-cart control is button: Add <N> to order inside the dialog that Quick Add opens. Naively clicking Quick Add and expecting the cart count to change will fail (observed: cart stayed at "0 carts" until "Add 1 to order" was clicked).
  • Cart button label encodes state, not a static word. It reads 0 carts when empty and <N> item / <N> items when populated — match on the presence of "cart"/"item", not an exact string, when locating it.
  • Go to checkout is an <a> link, not a button, and its literal href in the a11y urlMap is a decoy (https://www.ubereats.com/); the real navigation is JS-driven to /checkout, which bounces to the auth wall when logged out.
  • browse CLI 0.7.2 prints an "Update available: 0.7.2 -> 0.9.6 / Run: npm i -g browse@latest" banner that gets interleaved into command output (including browse snapshot). This is noise, not an error — filter it (grep -v) before parsing. This exact banner stalled an autobrowse inner-agent run: it treated the polluted snapshot as a failure and burned all 30 turns without reaching the menu. A stricter/streaming client should still fall back to browse get text body / direct pl deep-links when snapshot output looks corrupted.
  • Store hours / dayparts limit availability. The store page shows a daypart button (e.g. "Lunch 11:01 AM – 4:59 PM"); some items only appear during their daypart. A closed store or wrong daypart yields a menu with unavailable items.
  • Confirmed dead end — no anonymous API. Don't waste time trying to hit Uber's internal feed/store/checkout endpoints cookieless; they require Uber session cookies + x-csrf-token and are Cloudflare-fronted. The rendered web app is the surface.

Expected Output

// Cart assembled successfully, stopped at checkout boundary (primary success shape)
{
  "success": true,
  "delivery_address": "20 W 34th St, New York, NY 10001",
  "restaurant": "McDonald's® (Madison Square Garden (7th Ave & 33rd))",
  "store_url": "https://www.ubereats.com/store/mcdonalds-madison-square-garden-7th-ave-%26-33rd/VVyxLQxmQrK3Z9dlkZ0-bA",
  "cart": [
    { "item": "Double Cheeseburger", "qty": 1, "price": "$5.49" }
  ],
  "subtotal": "$5.49",
  "reached_checkout_review": true,
  "checkout_requires_login": true,
  "placed_order": false,
  "error_reasoning": null
}
// Reached the login/payment wall when confirming the boundary (still not placed)
{
  "success": true,
  "restaurant": "McDonald's®",
  "cart": [ { "item": "Double Cheeseburger", "qty": 1, "price": "$5.49" } ],
  "subtotal": "$5.49",
  "reached_checkout_review": true,
  "checkout_requires_login": true,
  "auth_wall_url": "https://auth.uber.com/v2/?...&redirect=%2Fcheckout",
  "placed_order": false,
  "error_reasoning": null
}
// Restaurant not found for this address
{
  "success": false,
  "delivery_address": "20 W 34th St, New York, NY 10001",
  "restaurant": "McDonald's",
  "cart": [],
  "placed_order": false,
  "error_reasoning": "No store matching 'McDonald's' delivers to this address."
}
// Blocked before the flow could start
{
  "success": false,
  "cart": [],
  "placed_order": false,
  "error_reasoning": "address_required | cloudflare_block | recaptcha_challenge | store_closed"
}