Ordering from GoPuff
Purpose
Build a GoPuff delivery order for a given US delivery address: set the delivery location, find products in the location-scoped catalog, add them to the bag, and read back the priced bag (line items, quantities, estimated subtotal) plus the delivery ETA — stopping at GoPuff's mandatory sign-in gate. This skill is read-only. It never signs in, never enters payment, and never places the final order — GoPuff has no guest checkout, so completing a purchase requires an authenticated account and a saved payment method, which are out of scope. Treat this as "assemble a priced cart and report it up to the checkout wall."
When to Use
- "How much would a bag of {items} cost from GoPuff delivered to {address}, and how fast?"
- Price-checking specific GoPuff SKUs at a specific delivery address (pricing and availability are location-scoped).
- Verifying that GoPuff delivers to an address and surfacing the delivery ETA.
- Pre-staging a cart for a human to review before they sign in and pay.
- NOT for: actually completing a purchase (requires account + payment; prohibited here), or order tracking after purchase.
Workflow
The only reliable surface is the browser. GoPuff is behind Kasada Monocle anti-bot; there is no usable public ordering API, and — critically — residential proxies make things worse (Kasada flags proxy/VPN egress IPs and serves "Monocle CAPTCHA Denied"). The working configuration is a --verified session with NO --proxies.
1. Create a verified (non-proxied) session
SID=$(browse cloud sessions create --keep-alive --verified \
| node -e "let s='';process.stdin.on('data',c=>s+=c).on('end',()=>process.stdout.write(JSON.parse(s).id))")
export BROWSE_SESSION="$SID"
Do not add --proxies. A proxied session gets Monocle CAPTCHA Denied ("Please disconnect from any VPNs or proxies"). A bare (non-verified) session renders a blank/challenge page. Only --verified alone reliably passes.
2. Load the homepage and confirm you cleared Kasada
browse open "https://www.gopuff.com/" --remote
browse wait timeout 8000 --remote
browse get title --remote # must be "Gopuff: 15-Min Delivery ..." — NOT "Just a moment..." or "Monocle CAPTCHA Denied"
If the title is a challenge string, the session config is wrong (see gotchas). Re-create with --verified and no proxies.
3. Set the delivery address (mandatory gate — no catalog/pricing/cart until this is done)
browse click "@<ref for 'Enter your address'>" --remote # header button, text "ENTER YOUR ADDRESS"
browse wait timeout 2500 --remote # opens /add-new-address dialog
browse click "@<ref for textbox: Address>" --remote
browse type "1500 Market St, Philadelphia" --remote # use `type`, NOT `fill` (fill submits early)
browse wait timeout 3000 --remote # Google Places suggestions render after ~3s
browse click "@<ref for the matching suggestion button>" --remote # e.g. "1500 Market St Philadelphia, PA, USA"
browse wait timeout 2000 --remote # confirm view: map + Apt/Suite + Home/Work
browse click "@<ref for 'Save and Finish'>" --remote
browse wait timeout 5000 --remote
Success indicator: the catalog renders with a header showing ARRIVES IN N MINS and the street name (e.g. 1500 MARKET ST). The address is stored in session cookies for the rest of the flow.
4. Find products and add them to the bag
Two ways to reach a product:
- Direct URL (fastest if you know the SKU):
https://www.gopuff.com/p/{slug}/p{productId}— e.g./p/coca-cola-12pk-12oz-can/p9697. - Search: click the
textbox: Search("Search Gopuff"), type a query, press Enter, and open a result link (same/p/{slug}/p{id}shape).
On the product page, read the heading: (product name) and the $X.XX price, then:
browse click "@<ref for 'Add to Bag'>" --remote
browse wait timeout 3000 --remote
# The header "MyBagButton" count increments (0 -> 1). Repeat for more items.
5. Open the bag and read the priced order
browse click "@<ref for 'MyBagButton'>" --remote # opens the bag drawer
browse wait timeout 2000 --remote
browse click "@<ref for 'My Bag'>" --remote # navigates to https://www.gopuff.com/bag
browse wait timeout 3000 --remote
browse snapshot --remote
The /bag page lists each line item (name, quantity, price), an Estimated Subtotal $X.XX, and a Checkout button. Extract these for the output.
6. Confirm the checkout gate and STOP
Clicking Checkout routes to https://www.gopuff.com/user-registration?from=bagRoot — a mandatory sign-in/sign-up wall (Continue with Google / Apple / Facebook, or phone/email). There is no guest checkout. Report checkout_state: "requires_signin" and stop. Do not authenticate, do not enter payment, do not place the order.
7. Release the session
browse cloud sessions update "$SID" --status REQUEST_RELEASE
Site-Specific Gotchas
- READ-ONLY. Never sign in, never enter payment, never click a final "Place Order". A complete purchase is impossible without a GoPuff account + payment method and is out of scope for this skill.
- Kasada Monocle anti-bot. GoPuff wraps every page in Kasada (
MCL.configure,/validate_spur_captcha; challenge titles "Just a moment..." → "Monocle CAPTCHA Denied"). A bare Browserbase session renders a blank/challenge page. --verifiedclears Kasada but is PROBABILISTIC — retry on denial. In testing, one verified session passed on first load; another identical verified session hit "Monocle CAPTCHA Denied" and only cleared after re-opening the URL and waiting ~12s (cleared by the 2nd–3rd attempt). If the title is "Monocle CAPTCHA Denied", do NOT give up:browse openthe URL again +browse wait timeout 12000and re-check the title, up to ~3 times, before recreating the session. Do not add proxies as a "fix" — that guarantees denial.- Proxies HURT — do not use
--proxies. Kasada explicitly flags proxy/VPN egress IPs: a--verified --proxiessession returns "Captcha validation failed. Please disconnect from any VPNs or proxies and try again." The observed egress IP on the proxied path was a datacenter IP that Kasada rejected.--verifiedalone (no proxies) is the only config that passed in testing. This is the opposite of most anti-bot sites — do not "escalate" to proxies here. - The host pre-run probe (Cloudflare/
likelyNeedsProxies: true) is misleading for this site. The real wall is Kasada, and proxies make it fail. Trust the empirical result: verified, no proxies. - Address gate is mandatory and comes first. No product prices, catalog, or cart function until a deliverable address is set via the
/add-new-addressdialog. Pricing and product availability are location-scoped, so always set the address before reading any price. - Use
browse type, notbrowse fill, in the address box.fillauto-submits and skips the Google Places typeahead;typethen a ~3s wait lets the suggestion buttons render so you can click the matching one. - Address autocomplete is Google Places. Suggestions appear ~3s after typing; each is a
button:whose accessible name is the full formatted address. Pick the one that matches; ignore business-name variants ("StrEATS of Philly...", "PIDC..."). - Product URL pattern is stable:
/p/{slug}/p{productId}(e.g.p9697). The numericp{id}is the SKU; the slug is cosmetic./near-me/{state}/{city}pages exist for SEO but the shoppable catalog is the location-gated app, not those pages. - Two-step bag open.
MyBagButtonopens a drawer; theMy Bagbutton inside it navigates to the full/bagpage where theEstimated SubtotalandCheckoutbutton live. - Snapshots are large and hydrate slowly. Always
browse wait timeout3000–8000ms after anybrowse openon gopuff.com before snapshotting — Kasada clearance + React hydration take a few seconds. GoPuff a11y snapshots are big (~190KB); pipe them throughgrepfor the refs/text you need rather than reading them whole. Ifbrowse snapshotappears to fail or truncate, fall back tobrowse get text bodyto confirm page state, then target elements by their accessible-name refs. - "Estimated" subtotal only. The
/bagpage shows an estimated subtotal (product prices, no markups/service fees per GoPuff's banner). Taxes, delivery fee, tip, and any promos are only computed after sign-in, which this skill does not reach — so the returned total is pre-tax/pre-fees. - No usable ordering API confirmed. All ordering happens through the Kasada-protected web app; there is no public JSON ordering endpoint to shortcut to. Don't waste time hunting for one.
Expected Output
// Primary success: cart assembled, priced, stopped at sign-in gate
{
"success": true,
"delivery_address": "1500 Market St, Philadelphia, PA, USA",
"delivery_eta_minutes": 19,
"bag": [
{ "name": "Coca-Cola 12pk 12oz Can", "product_url": "https://www.gopuff.com/p/coca-cola-12pk-12oz-can/p9697", "quantity": 1, "price": "$9.99" }
],
"estimated_subtotal": "$9.99",
"note": "Estimated subtotal is pre-tax and excludes delivery fee/tip, which are only computed after sign-in.",
"checkout_state": "requires_signin",
"error_reasoning": null
}
// Anti-bot wall (wrong session config — bare or proxied)
{
"success": false,
"checkout_state": null,
"error_reasoning": "Kasada Monocle blocked the session ('Monocle CAPTCHA Denied'). Use a --verified session with NO --proxies."
}
// Address not serviceable (GoPuff does not deliver to the entered address)
{
"success": false,
"delivery_address": "<entered address>",
"checkout_state": null,
"error_reasoning": "Address is outside GoPuff's delivery zone — no catalog/ETA rendered after saving the address."
}