One reservation CLI for OpenTable, Tock, and Resy — search each network at once, watch for cancellations, book, and track changes from a local store agents can query.
OpenTable, Tock, and Resy split the US fine-dining world between them and share zero data. This CLI unifies them: goat searches all three at once, watch polls each network for cancellations, earliest composes availability across all three, and drift surfaces what changed at a venue since your last look. Auth is auth login --chrome (for OT + Tock cookies) plus auth login --resy --email <you@example.com> (for the Resy API token) — no partner keys.
Authentication
Two distinct credentials, both managed by auth login:
- OpenTable + Tock are cookie-session networks. Anonymous reads (search, restaurant detail, availability) work out of the box via Surf with a Chrome TLS fingerprint that clears Akamai (OpenTable) and Cloudflare (Tock). For richer data — anything that requires being signed in — run
auth login --chrome once to import your already-logged-in cookies from your local Chrome profile.
- Resy uses a long-lived API token. Run
auth login --resy --email <you@example.com> once; you'll be prompted for your password with echo disabled, the CLI exchanges it for a JWT, and only the token persists in ~/.config/table-reservation-goat-pp-cli/session.json. The password is never stored. The shared public ResyAPI key (the same value every browser uses on resy.com) is hardcoded — there is nothing to register.
auth status shows the per-network state for all three. auth logout without arguments clears everything; auth logout --network resy clears just one network so the other two stay signed in.
Quick Start
# import Chrome cookies for OpenTable + Tock
table-reservation-goat-pp-cli auth login --chrome
# exchange email + password for a Resy API token (interactive password prompt)
table-reservation-goat-pp-cli auth login --resy --email you@example.com
# populate the local SQLite store from each network (restaurants, availability)
table-reservation-goat-pp-cli sync --full
# headline command — single ranked list across all three networks
table-reservation-goat-pp-cli goat 'omakase manhattan' --party 2 --when 'fri 7-9pm' --agent
# set up a cancellation watch and let the printer poll each network adaptively
table-reservation-goat-pp-cli watch add 'alinea' --party 2 --window 'sat 7-9pm' --notify local
# Resy watch — addressed by numeric venue id from `goat <name> --network resy`
table-reservation-goat-pp-cli watch add 'resy:1387' --party 2 --window 'fri 7-9pm' --notify local
# soonest open slot per venue. Bare slugs auto-resolve on OpenTable + Tock;
# Resy venues must be addressed explicitly by numeric id from `goat --network resy`
# because Resy uses numeric venue IDs that don't share slug-space with OT/Tock names.
table-reservation-goat-pp-cli earliest 'le-bernardin,atomix,smyth,alinea,resy:1387' --party 2 --within 14d --agent
# book on Resy (numeric venue id from search)
TRG_ALLOW_BOOK=1 table-reservation-goat-pp-cli book resy:1387 --date 2026-05-15 --time 19:30 --party 2 --agent
# cancel a Resy reservation (resy_token from book output)
table-reservation-goat-pp-cli cancel resy:<resy-token> --agent
Unique Features
These capabilities aren't available in any other tool for this API.
Cross-network ground truth
-
goat — One query across OpenTable, Tock, and Resy simultaneously, ranked by relevance, earliest availability, and price band.
When a user asks an agent to find a table, this is the single command that searches both reservation networks and returns structured ranked results — agents do not need to know which network covers which restaurant.
table-reservation-goat-pp-cli goat 'tasting menu chicago' --party 2 --when 'this weekend' --agent --select results.name,results.network,results.earliest_slot,results.price_band
-
earliest — Across a list of restaurants from either network, return the earliest open slot per venue within a time horizon.
When a user gives an agent a shortlist of venues and wants the soonest opportunity, this is the right shape — one structured response with one row per venue across all three networks.
table-reservation-goat-pp-cli earliest 'alinea,le-bernardin,smyth,atomix' --party 4 --within 21d --agent --select earliest.venue,earliest.network,earliest.slot_at,earliest.attributes
Local state that compounds
-
watch — Persistent local watcher that polls each network for openings on your target venues and party size, with notifications and optional auto-book.
Resy's Notify covers Resy only; tockstalk covers Tock only; restaurant-mcp's snipe covers Resy+OT only. None covers each network; none persists state. Use this when an agent or user needs a hot reservation that isn't currently available.
table-reservation-goat-pp-cli watch add 'le-bernardin' --party 2 --window 'Fri 7-9pm' --notify slack
-
drift — Show what changed at a specific venue since the last sync — new experiences, slot price moves, hours changes.
Hot-target deep-watch: when an agent or user is hunting one venue, drift surfaces every meaningful change since the last look.
table-reservation-goat-pp-cli drift alinea --since '2026-04-01' --agent
-
book — Place a reservation on OpenTable or Tock (v0.2). Free reservations only; payment-required venues return a typed payment_required error pointing at v0.3. Two safety layers: PRINTING_PRESS_VERIFY=1 short-circuits to dry-run regardless (verifier mock-mode floor), and live commit fires only when TRG_ALLOW_BOOK=1 is set. Without the env var, returns a dry-run envelope with a hint. Idempotency pre-flight via ListUpcomingReservations + normalized matching prevents double-book on retry; filesystem advisory lock keyed on (network, slug, date, time, party) prevents concurrent double-book across processes.
TRG_ALLOW_BOOK=1 table-reservation-goat-pp-cli book opentable:water-grill-bellevue --date 2026-05-13 --time 19:00 --party 2 --agent
Tock book uses chromedp-attach (drives a real Chrome session) since Tock's book flow uses traditional form-submit + Braintree CSRF. Card-required venues (most non-prepay Tock restaurants) prompt for CVC on stderr; the value flows through to the browser at confirm time. Free venues skip the CVC prompt. Requires Chrome running with --remote-debugging-port=9222, OR the CLI spawns a stealth headless Chrome as fallback. CVC can also be set via TRG_TOCK_CVC env var for non-interactive usage (MCP tool calls).
-
cancel — Cancel a reservation. NOT gated by TRG_ALLOW_BOOK (recovery action) but still respects the PRINTING_PRESS_VERIFY floor. Compound argument shape:
- OpenTable:
cancel opentable:<restaurantId>:<confirmationNumber>:<securityToken>
- Tock:
cancel tock:<venueSlug>:<purchaseId>
All compound parts are returned by the corresponding book command's JSON output.
table-reservation-goat-pp-cli cancel opentable:1255093:114309:01Ozsdas9H1Yx --agent
Usage
Run table-reservation-goat-pp-cli --help for the full command reference and flag list.
Commands
availability
Check open reservation slots across OpenTable, Tock, and Resy
table-reservation-goat-pp-cli availability check - Check open slots for a restaurant on a specific date and party size
table-reservation-goat-pp-cli availability multi-day - Multi-day availability for a single restaurant — Mon-Sun matrix
restaurants
Search and inspect restaurants across OpenTable, Tock, and Resy
table-reservation-goat-pp-cli restaurants get - Get a restaurant's full detail — hours, address, cuisine, price band, photos, accolades
table-reservation-goat-pp-cli restaurants list - List restaurants across OpenTable, Tock, and Resy; filter by location, cuisine, price band, accolades, and party size
watch
Persistent local cancellation watcher across all three networks
table-reservation-goat-pp-cli watch add - Register a watch for a venue, party size, and time window
table-reservation-goat-pp-cli watch list - List active watches
table-reservation-goat-pp-cli watch cancel - Cancel a watch by id
table-reservation-goat-pp-cli watch tick - Run one polling tick across all active watches (for cron / agents)
Output Formats
# Human-readable table (default in terminal, JSON when piped)
table-reservation-goat-pp-cli restaurants list
# JSON for scripting and agents
table-reservation-goat-pp-cli restaurants list --json
# Filter to specific fields
table-reservation-goat-pp-cli restaurants list --json --select id,name,neighborhood
# Dry run — show the request without sending
table-reservation-goat-pp-cli restaurants list --dry-run
# Agent mode — JSON + compact + no prompts in one flag
table-reservation-goat-pp-cli restaurants list --agent
Agent Usage
This CLI is designed for AI agent consumption:
- Non-interactive - never prompts, every input is a flag
- Pipeable -
--json output to stdout, errors to stderr
- Filterable -
--select id,name returns only fields you need
- Previewable -
--dry-run shows the request without sending
- Explicit retries - add
--idempotent to create retries and --ignore-missing to delete retries when a no-op success is acceptable
- Confirmable -
--yes for explicit confirmation of destructive actions
- Piped input - write commands can accept structured input when their help lists
--stdin
- Offline-friendly - sync/search commands can use the local SQLite store when available
- Agent-safe by default - no colors or formatting unless
--human-friendly is set
Exit codes: 0 success, 2 usage error, 3 not found, 5 API error, 7 rate limited, 10 config error.
Health Check
table-reservation-goat-pp-cli doctor
Verifies configuration and connectivity to the API.
Configuration
Config file: ~/.config/table-reservation-goat-pp-cli/config.toml
Static request headers can be configured under headers; per-command header overrides take precedence.
Power-user knobs (env vars)
OpenTable's WAF can rate-limit aggressive scans. The CLI ships with a disk cache, singleflight dedupe, and an AdaptiveLimiter so typical use never hits the limit. These env vars override the defaults:
| Env var | Default | Effect |
|---|
TRG_OT_CACHE_TTL | 3m | How long a cached availability response stays fresh. Range [1m, 24h]; out-of-range falls back to default with a stderr warning. |
TRG_OT_THROTTLE_RATE | 0.5 | Initial calls/second for the OT AdaptiveLimiter. Lower values pace harder (0.1 = 10s spacing); higher values are appropriate when routing through a personal proxy. Range [0.01, 5.0]. |
TRG_OT_NO_CACHE | unset | Set to 1 to bypass the cache by default. The --no-cache flag on earliest and watch tick does the same per-call. |
TRG_ALLOW_BOOK | unset | Live commit gate for book. Without it, book returns a dry-run envelope. cancel is NOT gated by this — it's a recovery action. |
PRINTING_PRESS_VERIFY | unset | Verifier-mode floor. When =1, both book and cancel short-circuit to dry-run regardless of TRG_ALLOW_BOOK. Set automatically by printing-press verify mock-mode subprocesses. |
TRG_TOCK_CVC | unset | When set, used as the CVC for Tock card-required bookings instead of prompting on stderr. Useful for MCP tool calls and other non-interactive contexts. |
TABLE_RESERVATION_GOAT_TOCK_CHROME_DEBUG_URL | http://localhost:9222 | Override for the Chrome DevTools endpoint used by the Tock chromedp-attach book flow. |
HTTPS_PROXY / HTTP_PROXY | unset | Standard Go-honored proxy URLs. Useful for routing OT traffic through a personal proxy or Tor SOCKS5 (socks5://localhost:9050). |
Troubleshooting
Not found errors (exit code 3)
- Check the resource ID is correct
- Run the
list command to see available items
API-specific
PersistedQueryNotFound 400 from OpenTable on first run — the persisted-query hash drifted; run table-reservation-goat-pp-cli doctor --refresh-hashes to bootstrap the current hash from a fresh homepage fetch
- Cloudflare challenge from exploretock.com — Surf transport with Chrome TLS clears this automatically; if you see a 403, run
table-reservation-goat-pp-cli doctor to verify the Surf fingerprint is loaded
Authentication required on a venue or detail call that needs sign-in — run table-reservation-goat-pp-cli auth login --chrome to import cookies, then auth status to confirm each network are signed in
- Empty availability results for a venue you know has openings — check
--party and --time (Tock returns empty when no slot matches the seating area filter); also try goat <venue> --debug to see the per-network response
- Watch never fires even though slots opened on the website — verify
watch list --json shows your watch state: active and last_polled_at recent; if the limiter is throttled the typed RateLimitError will be in the recent log — increase --cadence to back off
Sources & Inspiration
This CLI was built by studying these projects and resources:
Generated by CLI Printing Press