> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flynet.org/llms.txt
> Use this file to discover all available pages before exploring further.

# FAQ

> Common questions and gotchas.

## 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/*` (profile, status, wallets, tags,
check-ins, memberships) and `/payment_intents/*`. Use an API key for
restaurant and location Discovery and for the `/check_ins` venue feed
(including `/check_ins/{id}`). They are not interchangeable.

See [Authentication](/concepts/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:user_checkins` gates `/users/me/check_ins`, `read:wallets` gates
`/users/me/wallets`, and `read:memberships` gates `/users/me/memberships`.
(The `/check_ins` venue feed and `/check_ins/{id}` are API-key routes
gated by the separate `read:checkins` scope on the key.) 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 new
`refresh_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. (Earlier launch
builds required at least one filter; that requirement was dropped.)
`/check_ins` is an anonymized venue feed — 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, `?restaurants=...` 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 with `GET /payment_intents/{id}`
to see state changes.

### Does reusing an `idempotency_key` create a duplicate intent?

No. Per the API contract, reusing the same `idempotency_key` returns
the existing intent rather than creating a second one. A new key
creates a new intent.
