You need:
curl, an OAuth access token, and an API key. (/users/me/* resolves the subject from your JWT server-side, so no member ID is needed.)
This is the wire-level tour — three raw calls so you can see exactly what the API returns. When you build, use the TypeScript SDK instead of hand-rolling these requests:
@flynetdev/core handles both credentials, OAuth, errors, and pagination, and @flynetdev/react adds drop-in components. Building with an AI agent? Start at Build with AI.First course: set credentials
Two credentials are not interchangeable. Discovery routes (/restaurants*, /locations*) take the API key. Member-scoped routes (/users/me/*, /check_ins*, /memberships, /payment_intents*) take an OAuth access token. See OAuth for the full flow and API keys for the server-to-server side.
Tasting note -
/users/me/* is canonical for member-context calls. The JWT subject is resolved server-side; no UUID parameter needed. Decode the sub claim from your access token only if you need the member’s UUID for your own state.Second course: list restaurants
The simplest authenticated call. Pure server-to-server: no member context, no OAuth.total_count shifts as the seed list grows. Treat the exact number as illustrative.
A 401 from this route is JSON: {"status":401,"message":"...","error_code":"MISSING_API_KEY"}, not the empty-body 401 that OAuth routes return. Discovery routes reject OAuth bearer tokens.
Main course: fetch your wallets
Switches to OAuth. The subject is resolved from your JWT:/users/me/wallets returns the wallets of the token holder, no UUID in the path. Every Blackbird member has a MEMBERSHIP wallet and a SPENDING wallet, auto-provisioned on their first OAuth completion.
read:wallets scope. Wrong scope returns 403 with WWW-Authenticate: Bearer error="insufficient_scope": the route exists, your token just isn’t authorized for it. A 401 (rather than 403) returns an empty body with a WWW-Authenticate: Bearer header: the cause sits in the header, not in JSON.
Final course: your check-in history
Your own visit history, subject resolved from the JWT. Each row embeds the fulllocation, restaurant, and neighborhood, plus visit_number and timestamps: one call powers a complete feed row.
read:checkins scope. Wrong scope returns 403 with WWW-Authenticate: Bearer error="insufficient_scope": the route still exists, your token just isn’t authorized for it.
Tasting note - Pagination is zero-indexed. Start with
page=0. Timestamps are ISO 8601 strings.Tasting note - The collection route
/check_ins accepts optional filters ([restaurant, location, created_after, created_before]) — there is no user filter; the feed is anonymized. A bare GET /check_ins is valid and returns the full set. Unknown query params are silently ignored: a wrong name like restaurant_id= instead of restaurant= is treated as no filter supplied. created_after / created_before accept ISO 8601 only.Staging fixtures
Working staging UUIDs you can paste into the interactive API playground’s “Try It” buttons. These resources exist in staging seed data and are stable.| Resource | UUID | Notes |
|---|---|---|
| User (Ekow) | 13a014d0-31de-474b-89f9-d9a32b0d42b8 | 562 check-ins; 1 industry tag; MEMBERSHIP + SPENDING wallets. Member routes resolve the subject from your token (/users/me/*), so this UUID is reference only; it’s not a path parameter. |
| Restaurant (Anton’s) | 14339db3-2e7a-42c4-aa98-4c0fb18679eb | 57 check-ins; populated asset |
| Location (Anton’s West Village) | c54a3b6a-c31b-49b4-8af1-2dfb70ff3eec | 57 check-ins |
Hitting an error?
See Debugging: every observederror_code mapped to root cause + fix.
What’s next
Build with the SDK
The same three calls in typed TypeScript —
@flynetdev/core handles credentials, errors, and pagination for you.Ship with Claude
The full path with an AI agent: build on the SDK, handle secrets safely, deploy on Vercel.
Restaurant explorer
Chain restaurants → locations → weekly hours for a discovery surface.
OAuth, end-to-end
The full Token-Mediated Backend flow with PKCE and refresh rotation.