OAuth + API keys
Where do my client_id and client_secret come from?
After your app is approved, Blackbird sends them by email with your
registered redirect URI and allowed scopes.
What is the difference between OAuth and API keys?
Different routes require different credentials. Use OAuth for member-acting routes:/users/me/*, /check_ins*, /memberships,
and /payment_intents/*. Use an API key for restaurant and location
Discovery. They are not interchangeable.
See Authentication for the per-route table.
How do I read the signed-in member’s profile, wallets, or check-ins?
Call/users/me, /users/me/wallets, or /users/me/check_ins with
the member’s OAuth access token. The subject is resolved from the
token’s sub claim, so there’s no UUID in the path. You only need to
decode sub yourself if you want the member’s UUID for your own state.
Why do I get a 403 instead of a 401 on a member route?
The token is valid but doesn’t carry the scope that route requires.
read:profile gates /users/me + /users/me/status, read:checkins
gates /check_ins* + /users/me/check_ins, and read:wallets gates
/users/me/wallets. Re-authorize requesting the scope you need. The
403 body is empty; the reason is in the WWW-Authenticate header.
Why do my refresh tokens stop working after one use?
Refresh tokens are rotated. Each successful refresh returns a newrefresh_token that replaces the previous one. Store the new token
returned by /oauth/token and use it on the next refresh.
Why does my token exchange return invalid_grant?
The most common cause is that the authorization code was consumed
before your script reached it. Authorization codes are single-use
and short-lived. If your callback URL runs auth logic automatically,
the code may already be spent.
/check_ins filters
Do I need a filter on GET /check_ins?
No. A bare GET /check_ins is valid and returns the full paginated
set. Filters ([restaurant, location, created_after, created_before]) are optional and AND together. There is no user
filter — the feed is anonymized; passing user= is silently ignored.
(Earlier launch builds required at least one filter; that requirement
was dropped.) For the authenticated member’s own history, use
/users/me/check_ins.
Why does ?created_after=1715468700 return 400?
created_after and created_before take ISO 8601 strings, not
epoch seconds. Use 2026-04-01T00:00:00Z.
Why does ?some_filter=value return unfiltered results?
Unknown query parameters are silently ignored. Check the parameter
name. For example, ?restaurant_id=... instead of ?restaurant=...
is silently dropped, so you get the full unfiltered set rather than
an error, which means more rows than you expected.
Payments
Why does confirm return 400 payment0030?
The customer does not hold enough FLY in their SPENDING wallet.
v1 does not card-fund or auto-load FLY. Pre-check the balance or
surface the error to the member.
Can I do partial refunds?
Not in v1. Refunds reverse the full amount of a paid intent. Partial refunds are on the roadmap.Do I get a webhook when a payment confirms?
Not in v1. Poll the intent’s status withGET /payment_intents/{id}
to see state changes.
Why does my idempotent replay return 200 instead of 201?
The sameidempotency_key on the same flynet_merchant_id returns
the existing intent with HTTP 200. A new key creates a new intent
with HTTP 201.