{"openapi":"3.1.0","info":{"title":"Registry — agent-native merchant registry (REST mirror)","version":"1.0.0","description":"Structured merchant data and phone-fulfilled restaurant reservations for AI agents. San Francisco, CA, ~500 merchants. This REST surface mirrors the MCP server at POST /mcp (Streamable HTTP) — same data, same booking state machine. Honest by design: filter-based search in deterministic order (never ranked), transaction-verified feedback only, availability checked on the call at booking time (`performed_at_booking`). Reads are free and unauthenticated; API keys are optional and exist for abuse control, not gating."},"servers":[{"url":"https://agentic-commerce-registry.fly.dev"}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"Optional developer key from POST /v1/keys. Also accepted as Authorization: Bearer <key>."}},"schemas":{"MerchantSummary":{"type":"object","description":"Compact merchant record returned by search. Deterministic order, never ranked.","properties":{"merchant_id":{"type":"string","format":"uuid"},"name":{"type":"string"},"cuisine_tags":{"type":"array","items":{"type":"string"}},"attribute_tags":{"type":"array","items":{"type":"string"}},"neighborhood":{"type":"string"},"address":{"type":"string"},"lat":{"type":"number"},"lng":{"type":"number"},"price_band":{"type":"integer","minimum":1,"maximum":4},"reservation_policy":{"type":"string","enum":["walk_in_only","phone_reservations","accepts_reservations","requires_deposit"]},"bookable":{"type":"boolean","description":"Derived: phone-verified AND accepts phone reservations AND not opted out AND no deposit required."},"verification_status":{"type":"string","enum":["unverified","phone_verified","transaction_verified"]},"distance_km":{"type":"number","description":"Present only when a geo filter was supplied."}}},"BookingStatus":{"type":"object","description":"Booking state machine position with structured outcome details.","properties":{"booking_id":{"type":"string","format":"uuid"},"merchant_id":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["pending","queued","in_progress","needs_input","confirmed","failed","cancelled"]},"failure_reason":{"type":"string","enum":["no_answer","fully_booked","closed","policy_mismatch","merchant_declined","bad_data","expired_sla","needs_input_timeout"]},"requested_time":{"type":"string"},"window_minutes":{"type":"integer"},"party_size":{"type":"integer"},"confirmed_time":{"type":"string"},"confirmation_code":{"type":"string"},"merchant_instructions":{"type":"string"},"needs_input_options":{"type":"array","items":{"type":"object","properties":{"time":{"type":"string"},"note":{"type":"string"}}},"description":"Merchant-offered alternatives awaiting the agent's decision (resolve via /v1/bookings/{id}/modify)."},"needs_input_deadline":{"type":"string"},"attempts":{"type":"integer"},"sla_deadline":{"type":"string","description":"Terminal state guaranteed by this time (REQ-FUL-6)."},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}}},"paths":{"/v1/meta":{"get":{"summary":"Registry metadata: coverage, freshness, schema version, ordering rule","description":"Evaluate the registry itself — merchant/bookable counts, verification and freshness stats (including how stale the data is), feedback corpus size, and the documented deterministic ordering rule.","responses":{"200":{"description":"Registry metadata."}}}},"/v1/stats":{"get":{"summary":"Aggregate operational stats (PII-free): booking funnel, completion rate, failure reasons","description":"Public health/ops feed: booking counts by state, completion rate, median confirmation time, structured failure reasons, voice-agent containment, feedback corpus size. Contains no reservation, contact, or per-merchant data.","responses":{"200":{"description":"Aggregate operational stats."}}}},"/healthz":{"get":{"summary":"Liveness/readiness probe for uptime monitors","responses":{"200":{"description":"{ok: true, merchants, schema_version, uptime_s}."},"503":{"description":"{ok: false} — database unavailable."}}}},"/.well-known/mcp.json":{"get":{"summary":"Discovery manifest for MCP/agent directories and crawlers","description":"Machine-readable service card: MCP endpoint + transport, tool list, REST index, data policy (including honest current-data status), coverage.","responses":{"200":{"description":"Discovery manifest."}}}},"/v1/merchants":{"get":{"summary":"Filter-based merchant search (NOT ranked; deterministic order)","description":"Order: merchant_id ASC by default; distance ASC (ties by merchant_id) when lat/lng given with order_by=distance. The registry never ranks (REQ-DATA-2).","parameters":[{"name":"neighborhood","in":"query","schema":{"type":"string"}},{"name":"lat","in":"query","schema":{"type":"number"}},{"name":"lng","in":"query","schema":{"type":"number"}},{"name":"radius_km","in":"query","schema":{"type":"number","default":3}},{"name":"cuisine_tags","in":"query","schema":{"type":"string"},"description":"Comma-separated; matches ANY."},{"name":"attribute_tags","in":"query","schema":{"type":"string"},"description":"Comma-separated; matches ALL."},{"name":"price_band_min","in":"query","schema":{"type":"integer","minimum":1,"maximum":4}},{"name":"price_band_max","in":"query","schema":{"type":"integer","minimum":1,"maximum":4}},{"name":"open_at","in":"query","schema":{"type":"string"},"description":"ISO local datetime; only merchants open then."},{"name":"bookable_only","in":"query","schema":{"type":"boolean"}},{"name":"party_size","in":"query","schema":{"type":"integer","minimum":1}},{"name":"order_by","in":"query","schema":{"type":"string","enum":["merchant_id","distance"]}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}}],"responses":{"200":{"description":"Deterministically ordered page of compact merchant records.","content":{"application/json":{"schema":{"type":"object","properties":{"order":{"type":"string","enum":["merchant_id","distance"]},"total":{"type":"integer"},"results":{"type":"array","items":{"$ref":"#/components/schemas/MerchantSummary"}}}}}}}}}},"/v1/merchants/{merchant_id}":{"get":{"summary":"Full signal dump for one merchant — maximal data, zero opinion","description":"Every schema field plus structured hours, raw feedback history, aggregate feedback summary, platform-observed operational stats, and per-field provenance with timestamps.","parameters":[{"name":"merchant_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Full merchant record."},"404":{"description":"unknown_merchant"}}}},"/v1/export/merchants.ndjson":{"get":{"summary":"Bulk export of the whole registry (NDJSON) — encouraged, not fought","description":"Whole-city ingestion for serious agent developers (REQ-MCP-5). Open data is the adoption strategy. Opted-out merchants are excluded.","responses":{"200":{"description":"One merchant JSON object per line.","content":{"application/x-ndjson":{}}}}}},"/v1/keys":{"post":{"summary":"Self-serve developer key (free; abuse control, not gating)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["developer_name","contact"],"properties":{"developer_name":{"type":"string","maxLength":80},"contact":{"type":"string","maxLength":120},"webhook_url":{"type":"string","description":"Optional http(s) URL for booking state-change webhooks."}}}}}},"responses":{"200":{"description":"New key (reg_...) and documented rate limit."},"400":{"description":"Validation error."},"429":{"description":"Daily minting cap reached for this client."}}}},"/v1/bookings":{"post":{"summary":"Place a reservation (async — a call is placed to the merchant)","description":"Returns booking_id with state=queued immediately. Poll GET /v1/bookings/{id} or supply callback_url for webhooks. With accept_within_window=true (recommended), merchant counter-offers inside ±window_minutes auto-confirm without a round-trip.","security":[{},{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["merchant_id","party_size","datetime","reservation_name"],"properties":{"merchant_id":{"type":"string","format":"uuid"},"party_size":{"type":"integer","minimum":1},"datetime":{"type":"string","description":"Requested time, ISO local datetime, e.g. 2026-07-18T19:00"},"window_minutes":{"type":"integer","minimum":0,"maximum":240},"accept_within_window":{"type":"boolean"},"reservation_name":{"type":"string"},"contact":{"type":"string","description":"Optional phone/email for confirmation relay to the end human."},"special_requests":{"type":"string","maxLength":280},"callback_url":{"type":"string","description":"Webhook URL for booking state-change events."}}}}}},"responses":{"201":{"description":"Accepted: {ok, booking_id, state: queued}."},"400":{"description":"Structured rejection, e.g. merchant_not_phone_verified, requires_deposit_not_supported_in_v1, party_size_out_of_range."},"401":{"description":"Invalid or throttled API key (keys are optional; omit rather than guess)."}}}},"/v1/bookings/{booking_id}":{"get":{"summary":"Booking status: state machine position + structured outcome","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"include_events","in":"query","schema":{"type":"boolean"},"description":"Include the full audit log."}],"responses":{"200":{"description":"Booking status.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingStatus"}}}},"404":{"description":"unknown_booking"}}}},"/v1/bookings/{booking_id}/modify":{"post":{"summary":"Amend a booking / resolve needs_input","description":"In needs_input: pass accept_option_index to take a merchant-offered time (confirms immediately) or amend datetime/party_size to re-queue. Modifying a confirmed booking cancels and rebooks (new booking_id returned).","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"datetime":{"type":"string"},"party_size":{"type":"integer","minimum":1},"window_minutes":{"type":"integer","minimum":0,"maximum":240},"accept_option_index":{"type":"integer","minimum":0}}}}}},"responses":{"200":{"description":"Updated state."},"400":{"description":"Structured error."}}}},"/v1/bookings/{booking_id}/cancel":{"post":{"summary":"Cancel a booking (mandatory when plans change — no-shows are tracked)","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"Cancelled."},"400":{"description":"Already terminal."}}}},"/v1/bookings/{booking_id}/feedback":{"post":{"summary":"Post-visit feedback (confirmed bookings only, once, within 14 days)","description":"Served raw to all agents via the merchant record — never editorialized, never scored (REQ-FBK-3).","parameters":[{"name":"booking_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["reservation_honored"],"properties":{"reservation_honored":{"type":"boolean"},"seated_on_time":{"type":"boolean"},"matched_description":{"type":"boolean"},"would_repeat":{"type":"boolean"},"free_text":{"type":"string","maxLength":500}}}}}},"responses":{"201":{"description":"Recorded."},"400":{"description":"Structured rejection (not confirmed / window expired / duplicate)."}}}}},"x-rate-limits":{"per_minute":300,"headers":["X-RateLimit-Limit","X-RateLimit-Remaining","X-RateLimit-Reset"]},"x-mcp":{"endpoint":"/mcp","transport":"streamable-http","note":"Agents with MCP support should prefer the MCP endpoint; tools self-describe via tools/list."}}