Partner App Installs
Use this guide when you want merchants to connect your product to Flint through a hosted install flow.
At a high level:
- create a partner app
- send a merchant to Flint's hosted authorize URL
- receive an authorization code at your redirect URI
- exchange the code for a partner install access token
- call normal Flint
/v1/...APIs with that token - optionally subscribe to partner app webhooks
If you still need your first Flint merchant auth, complete API & Agent Onboarding first or use the dashboard to create a merchant and API key.
Before You Start
- A Flint merchant account you control
- Either a
developer_session_tokenor an external API key withaccounts.merchants.write - At least one redirect URI you control
- A backend that can safely store
client_secret, refresh tokens, and webhook secrets
Treat state as an opaque CSRF and request-correlation value. Generate it per install attempt and validate it on the callback.
Step 1: Create The Partner App
curl -X POST https://api.withflintpay.com/v1/developer/partner/apps \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY_OR_DEVELOPER_SESSION" \
-d '{
"name": "Acme OMS",
"app_type": "server",
"visibility": "private",
"redirect_uris": [
"https://acme.example.com/flint/oauth/callback",
"http://localhost:3000/flint/oauth/callback"
],
"permission_manifest": [
{
"permission_id": "manage_orders",
"title": "Manage orders",
"description": "Read and update Flint orders for installed merchants.",
"required_scopes": [
"commerce.orders.read",
"commerce.orders.write"
]
},
{
"permission_id": "read_customers",
"title": "Read customers",
"description": "Read customer records for installed merchants.",
"required_scopes": [
"customers.customers.read"
],
"optional": true
}
],
"default_requested_permissions": [
"manage_orders"
]
}'
Store:
data.partner_app_iddata.client_iddata.client_secret
client_secret is shown once. Flint cannot return the same value later.
Step 2: Build The Hosted Install URL
Send the merchant to:
https://api.withflintpay.com/v1/oauth/authorize
with these query parameters:
response_type=codeclient_idredirect_urimode=test|livestate- optional
permission_ids - optional
environment_id
Example:
https://api.withflintpay.com/v1/oauth/authorize?response_type=code&client_id=fpc_0123456789abcdef&redirect_uri=https%3A%2F%2Facme.example.com%2Fflint%2Foauth%2Fcallback&mode=test&state=merchant-session-42&permission_ids=manage_orders,read_customers
Rules to remember:
redirect_urimust exactly match a registered redirect URI- if
permission_idsis omitted, Flint usesdefault_requested_permissions - all non-optional permissions are always granted
- if
environment_idis omitted, Flint uses the merchant's default environment for the selected mode - live installs require the merchant to have completed onboarding
Optional: Preview Before Redirecting
If you want to validate the request or show a preflight summary in your own product, call:
curl "https://api.withflintpay.com/v1/oauth/authorize/preview?response_type=code&client_id=FPC_CLIENT_ID&redirect_uri=https%3A%2F%2Facme.example.com%2Fflint%2Foauth%2Fcallback&mode=test&state=merchant-session-42&permission_ids=manage_orders,read_customers"
The preview response returns the app metadata plus the exact permission set Flint will request from the merchant.
Step 3: Handle The Callback
On approval, Flint redirects back to your redirect_uri with:
codestatemode
Example:
https://acme.example.com/flint/oauth/callback?code=fpac_abc123&state=merchant-session-42&mode=test
On a redirectable failure, Flint sends:
errorerror_descriptionstate
Example:
https://acme.example.com/flint/oauth/callback?error=access_denied&error_description=merchant+denied+install&state=merchant-session-42
On the callback:
- validate that
statematches the value you issued - reject the callback if
erroris present - store
modealongside the install - exchange
codeimmediately on your backend
Step 4: Exchange The Authorization Code
Use your app's client_id and client_secret.
curl -X POST https://api.withflintpay.com/v1/oauth/token \
-u "FPC_CLIENT_ID:FPS_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=FPAC_AUTHORIZATION_CODE" \
-d "redirect_uri=https://acme.example.com/flint/oauth/callback"
Example response:
{
"access_token": "eyJ...",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "fprt_abc123",
"scope": "commerce.orders.read commerce.orders.write customers.customers.read",
"merchant_id": "mer_123",
"partner_app_id": "papp_123",
"partner_app_install_id": "pinst_123",
"environment_grant_id": "egrt_123",
"mode": "test"
}
Store:
refresh_tokenmerchant_idpartner_app_install_idenvironment_grant_idmode- granted
scope
Step 5: Call Flint APIs With The Install Token
Use the returned access token on the normal Flint public API.
curl https://api.withflintpay.com/v1/orders \
-H "Authorization: Bearer PARTNER_INSTALL_ACCESS_TOKEN"
The partner install token only works for scopes granted during install.
Examples:
- if the install grants
commerce.orders.read, you can read orders - if the install grants
payments.payment_intents.read, you can read payment intents - if the install does not grant a route's required scope, Flint returns
403 insufficient_scope
Do not send a partner install token and an external API key on the same request.
Step 6: Refresh Tokens
When the access token expires, rotate the refresh token:
curl -X POST https://api.withflintpay.com/v1/oauth/token \
-u "FPC_CLIENT_ID:FPS_CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=FPRT_REFRESH_TOKEN"
Replace the stored refresh token with the new one from the response.
Step 7: Subscribe To Partner Webhooks
The current webhook model is one endpoint per partner app. When you create that endpoint, choose one pattern:
- app lifecycle webhooks, to track installs and revocations
- installed-merchant event webhooks, to receive Flint resource events for installed merchants
Option A: App lifecycle webhook
curl -X POST https://api.withflintpay.com/v1/developer/partner/apps/PAPP_ID/webhook \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY_OR_DEVELOPER_SESSION" \
-d '{
"url": "https://acme.example.com/flint/partner-webhooks",
"source_type": "app",
"enabled": true
}'
If event_types is omitted for source_type=app, Flint subscribes you to all lifecycle events:
partner_app.install.createdpartner_app.install.updatedpartner_app.install.permissions_updatedpartner_app.install.revokedpartner_app.install.environment_grant.createdpartner_app.install.environment_grant.revoked
Option B: Installed-merchant webhook
curl -X POST https://api.withflintpay.com/v1/developer/partner/apps/PAPP_ID/webhook \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY_OR_DEVELOPER_SESSION" \
-d '{
"url": "https://acme.example.com/flint/merchant-events",
"source_type": "installed_merchants",
"mode": "both",
"event_types": [
"order.payment_succeeded",
"payment_intent.succeeded",
"refund.created"
],
"enabled": true
}'
For source_type=installed_merchants:
event_typesis requiredmodemust betest,live, orboth- Flint only forwards an event if the install has the read scope required for that event type
If you need both lifecycle events and installed-merchant event forwarding today, you need to choose which category Flint sends directly for that app and fill the other gap from your own control plane or install inspection routes.
For example:
order.payment_succeededneedscommerce.orders.readpayment_intent.succeededneedspayments.payment_intents.readrefund.createdneedscommerce.refunds.readrefund.failedneedscommerce.refunds.read
Inspect Installs Later
Use these routes from your merchant auth context:
GET /v1/developer/partner/appsGET /v1/developer/partner/apps/{partner_app_id}GET /v1/developer/partner/apps/{partner_app_id}/installsGET /v1/developer/partner/apps/{partner_app_id}/installs/{partner_app_install_id}
Use revocation routes when the merchant disconnects:
POST /v1/developer/partner/apps/{partner_app_id}/installs/{partner_app_install_id}/revokePOST /v1/developer/partner/apps/{partner_app_id}/installs/{partner_app_install_id}/environment-grants/{environment_grant_id}/revoke
Production Checklist
- store
client_secret, refresh tokens, and webhook secrets securely - generate a unique
statevalue per install attempt - validate
stateon every callback - exchange authorization codes only on your backend
- record
merchant_id,partner_app_install_id,environment_grant_id, andmode - use webhook deliveries as the durable signal for install lifecycle changes
- refresh tokens server-side and rotate stored refresh tokens atomically
- test both
testandliveinstalls explicitly
Next Steps
- Partner Apps API reference for the full route surface
- Webhooks guide for signature verification patterns
- Rate Limits for retry and worker planning
