Skip to main content
Flynet uses two credentials for partner integrations. Which one you use depends on which routes you call.

Pick by route

Route familyCredential
/restaurants* and /locations*API key in X-API-Key
/users/me/* (profile, status, wallets, check-ins)OAuth access token in Authorization: Bearer
/check_ins (global feed)API key in X-API-Key, minted with the read:checkins scope
/check_ins/{id}OAuth access token in Authorization: Bearer
/membershipsOAuth access token in Authorization: Bearer
/payment_intents/*OAuth access token in Authorization: Bearer
The two credentials are not interchangeable. Sending an OAuth bearer to /restaurants returns 401 MISSING_API_KEY. Sending an API key to /users/me/check_ins is rejected — a member’s own history always needs their token.
From the kitchen - Reach for the API key first when you are building app-level discovery, such as a map of nearby restaurants, a catalog page, hours, or locations. Reach for OAuth when you are doing anything with a member’s wallets, tags, check-ins, or payments.

What each credential authorizes

The API key is issued per partner, per environment, and grants app-level read access to Discovery data. The OAuth access token acts on behalf of the member who completed the flow. Member routes resolve the subject from the token’s sub claim, so /users/me/* returns that member’s data, never another member’s.
CredentialReadsWrites
API keyAny restaurant, any location, any open hours in the environment it was issued for; the anonymized check-in feed (with the read:checkins scope)None at v1
OAuth access tokenThe authenticated member’s own profile, status, wallets, tags, and check-ins; the memberships listPayment Intents on behalf of your merchant

OAuth scopes

Member routes are gated by scope. A token without the required scope returns 403 with WWW-Authenticate: Bearer error="insufficient_scope": the route still exists; the token simply isn’t authorized for it.
ScopeGates
read:profile/users/me, /users/me/status
read:checkins/check_ins/{id}, /users/me/check_ins; also required on the API key for the /check_ins feed
read:wallets/users/me/wallets
API keys carry scopes too: they are set in the body of the key-mint request, and the app’s allowed_scopes are not inherited. A key minted without read:checkins gets 403 insufficient_scope on the /check_ins feed. The member who completed the OAuth flow is the token’s sub claim. See Identify the authenticated member.

Credential lifetimes

CredentialLifetimeRefresh path
API keyDoesn’t expire until revokedSupport to revoke + reissue
OAuth access token60 minutesPOST /oauth/token with grant_type=refresh_token; see OAuth Step 4
OAuth refresh tokenUp to 30 days, rotated on every useSame call rotates it

How credentials reach you

After your app is approved, Blackbird sends your credentials by email — a set scoped to staging. Production credentials are issued separately when you’re approved for live traffic; see Environments.
FieldExampleUsed for
client_iddf1f9d01-… (UUID)OAuth /authorize and /token. Public: safe to embed in browsers when paired with PKCE.
client_secretopaque ~40-char stringBackend /token exchange only. Never expose to a browser.
API key40-char string with bb_test_ or bb_live_ prefixDiscovery routes (/restaurants*, /locations*). Send as X-API-Key. Backend only. The prefix is a labeling hint; env binding is server-side. Use the key you received.
API key hintlast 4 chars (e.g. mgK9)Reference a key in support tickets without revealing the full value.
Registered redirect URI(s)e.g. https://yourapp.com/oauth/callbackWhere /oauth/authorize redirects after consent. Must match exactly. Multiple allowed.
Approved scopese.g. read:profile read:checkins read:walletsThe scope set your app may request. Each member route is gated by scope; a token missing the required scope returns 403 insufficient_scope.
flynet_merchant_id (if payments-enabled)UUIDRequired body field on every POST /payment_intents. Per-partner, per-env.
Allowlisted CORS origin(s)e.g. https://yourapp.comBrowser origin(s) cleared to call the API directly. Only needed for direct browser → API.
If anything is missing or wrong, reply to the onboarding email; fixes are fast pre-launch. See OAuth for the full OAuth flow and API keys for server-to-server use.
Chef’s warning - client_secret, API keys, and flynet_merchant_id are secrets. Never expose them in client-side code, public repos, or screenshots. client_id and registered redirect URIs are public.

401 behavior

The 401 envelope is determined by the route family’s gating filter, not by which credential you happened to send.
  • OAuth-protected routes (/users/*, /check_ins/{id}, /payment_intents/*) return HTTP 401 with an empty body on missing or invalid bearer. This also happens if you accidentally send an API key here: the OAuth filter doesn’t see a bearer and rejects. Do not try to parse JSON.
  • API-key routes (/restaurants*, /locations*, the /check_ins feed) return HTTP 401 with the MISSING_API_KEY envelope if the API key is missing or invalid. See Pagination + errors for the exact shape.
See Debugging for every observed error_code mapped to cause and fix.

Choose your on-ramp

Hello API Key

One curl, one API key, confirm Discovery works.

Hello OAuth

One curl, one access token, confirm a member route works.