Why OAuth exists here
Your application can already do everything the Fluz API offers on your own account. You mint a token with your API key and go — see Authentication and API credentials. An OAuth application is what you need when the account isn’t yours. The moment you want to issue a card on a customer’s wallet, pull from their linked bank account, read their transactions, or pay them out, you need that person’s explicit permission — and you need it in a form Fluz can verify, scope, expire, and revoke. That’s what an OAuth application is: a registered identity for your software, plus a consent mechanism that turns a user’s approval into a token your server can use.Once you hold a customer-scoped token, the API is identical.
createVirtualCard on your own token creates a card on your wallet; the same mutation on a customer token creates it on theirs. OAuth changes whose account you’re touching, not what you can do.Do you need one?
- No — your own account only
- Yes — customer accounts
- You already have one
You’re issuing cards, buying gift cards, or moving money within your own Fluz account: a disbursement engine, a bulk card run, an internal spend tool, an ERP sync.Use your application API key to call
generateUserAccessToken directly. No OAuth app, no consent screen, no redirect. Start at API credentials.Three sets of credentials, three different jobs
The most common source of confusion in this section is that a Fluz application carries more than one credential pair, and they are not interchangeable.The permission model
Fluz enforces permissions at two levels, and an application’s effective access is the intersection of the two.1
App-level grant — the ceiling
Set on the Permissions tab of your app. This is the maximum your application may ever request, independent of any user. A scope you haven’t enabled here is silently ignored if you put it in an authorize URL — the request won’t error, the scope simply won’t be granted.Some scopes are administered by Fluz rather than self-selected.
PCI_COMPLIANCE is granted at the application level only, to developers who have demonstrated PCI DSS compliance, and cannot be requested when generating a token.2
User-level grant — the floor
Set by the end user on the consent screen. They see the scopes you requested — grouped under readable top-level headers rather than raw enum values — and approve them. Anything they decline is not granted.
3
Both must be live
Validated at
generateUserAccessToken, not at call time. Both grants must exist and be unexpired. A revoked or lapsed grant therefore surfaces as a token generation failure, not as a permission error midway through a flow — which is usually the first place to look when a previously working integration stops working.
Use
getApplicationScopes to read what’s currently granted. Full reference: Application Scopes.
Ask for less. A shorter consent screen converts better, and a narrower token limits the damage if it leaks. Request what the flow in front of you needs and mint a new token when you need more.
The lifecycle, end to end
Each step below is a deep-dive page in this section. This is the map; the pages are the territory.1
Create the app
From the developer dashboard, choose Browse templates and add the OAuth Integration template. Name it, subtitle it, describe it — those three fields are what your users will see on the consent screen, so write them for a human, not for your issue tracker.→ Create an OAuth App
2
Configure it
On the Permissions tab, select your scope ceiling. On the OAuth tab, set your Redirect URIs (public, no query parameters, any number of them) and your Webhook URLs (each optionally subscribed to specific events; a URL with none selected becomes a catch-all). Add an avatar and logomark on Overview — the consent screen looks unfinished without them.→ Configure OAuth App
3
Send the user to authorize
Redirect to
/authorize with response_type=code, your client_id, a registered redirect_uri, a space-delimited scopes list, and an optional state value you want handed back to you untouched.→ Client-facing OAuth grant flow4
Receive the code
On approval, Fluz redirects to your
redirect_uri with code and your original state. On a misconfiguration, the redirect carries an error message describing what didn’t match.5
Exchange the code for tokens
Call
/token/exchange with the code and the exact same redirect_uri, authenticated with Authorization: Basic base64(client_id:client_secret). You get back an accessToken, a refreshToken, expiry timestamps, and the confirmed scope array.→ Exchanging an OAuth authorization code6
Refresh, don't re-prompt
Call
/token/refresh with the refresh_token and the same Basic auth header. Access tokens are deliberately short-lived — on the order of ten minutes — while refresh tokens last roughly a month. Refresh silently in the background; only send a user back through consent when the refresh token itself has expired or the grant has been revoked.→ Refreshing an OAuth accessToken7
Go live
Staging and production are separate environments with separate applications and separate credentials. Nothing carries over — you register the app, the redirect URIs, and the webhook endpoints again against production hosts.→ Deploying to Production
Rules that bite
Worth internalizing before you start, because each of these fails quietly or confusingly.Redirect URIs must match exactly — twice
Redirect URIs must match exactly — twice
The
redirect_uri you send to /authorize must be registered on your app, and the one you send to /token/exchange must be byte-identical to the one you used at /authorize. Trailing slashes, http vs https, and host casing all count. Register no query parameters on the URI itself — use state to carry context instead.Unenabled scopes are ignored, not rejected
Unenabled scopes are ignored, not rejected
Request a scope you haven’t checked on the Permissions tab and the authorize request still succeeds — the scope is dropped. Always read the
scope array in the exchange response and treat it, not your request, as the truth about what you can do.Basic auth is base64 of the pair, not of each part
Basic auth is base64 of the pair, not of each part
Authorization: Basic <base64(client_id + ":" + client_secret)>. Encode the joined string. Most integration failures at the exchange step are this.`state` is your only channel back
`state` is your only channel back
The redirect is a fresh browser navigation. If you need to know which user, which flow, or which page to return to, put a signed or server-looked-up reference in
state. Don’t put anything sensitive in it — it travels through the user’s browser.Token failures are usually grant failures
Token failures are usually grant failures
If
generateUserAccessToken starts failing for a user who worked yesterday, check whether the app-level grant or the user-level grant expired or was revoked, before you go looking at your code.OAuth apps vs. widgets
Both are applications. Both use the permission model above. The difference is who builds the consent surface.
You can mix them: register and KYC users over the API, then open a widget only for consent and sensitive capture. See Embedded Widgets for the hybrid patterns.
Next steps
Create an OAuth app
Register your application from the OAuth Integration template.
Configure OAuth app
Scopes, redirect URIs, webhooks, branding.
Client-facing grant flow
Build the authorize URL and handle the callback.
Exchange an authorization code
Turn a code into an access token and refresh token.
Refresh an access token
Stay authorized without re-prompting the user.
Deploy to production
Re-register against live hosts and go live.
Building a platform where every one of your customers has a Fluz account? Build a platform walks the whole pattern end to end, and every capability on API Features works identically on a connected account.