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. API-key routes (/restaurants*, /locations*, and the /check_ins venue feed including /check_ins/{id}) take the API key. Member-scoped routes (/users/me/*, /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 missing key returns an empty-body 401 with a WWW-Authenticate: Bearer header (reason in the header, not JSON). An invalid key returns a JSON 401 with code: "invalid_api_key". Discovery routes reject OAuth bearer tokens either way.
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 timestamps: one call powers a complete feed row.
read:user_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 is an anonymized venue feed (no member identity) and accepts optional filters ([restaurant, location, created_after, created_before]), but a bare GET /check_ins is valid and returns the full set. Unknown query params are silently ignored: a typo like restaurants= 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.
Staging data may change without notice. If a UUID stops working, ping Support.
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.