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:

  1. create a partner app
  2. send a merchant to Flint's hosted authorize URL
  3. receive an authorization code at your redirect URI
  4. exchange the code for a partner install access token
  5. call normal Flint /v1/... APIs with that token
  6. 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_token or an external API key with accounts.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

Bash
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_id
  • data.client_id
  • data.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:

text
https://api.withflintpay.com/v1/oauth/authorize

with these query parameters:

  • response_type=code
  • client_id
  • redirect_uri
  • mode=test|live
  • state
  • optional permission_ids
  • optional environment_id

Example:

text
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_uri must exactly match a registered redirect URI
  • if permission_ids is omitted, Flint uses default_requested_permissions
  • all non-optional permissions are always granted
  • if environment_id is 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:

Bash
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:

  • code
  • state
  • mode

Example:

text
https://acme.example.com/flint/oauth/callback?code=fpac_abc123&state=merchant-session-42&mode=test

On a redirectable failure, Flint sends:

  • error
  • error_description
  • state

Example:

text
https://acme.example.com/flint/oauth/callback?error=access_denied&error_description=merchant+denied+install&state=merchant-session-42

On the callback:

  1. validate that state matches the value you issued
  2. reject the callback if error is present
  3. store mode alongside the install
  4. exchange code immediately on your backend

Step 4: Exchange The Authorization Code

Use your app's client_id and client_secret.

Bash
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:

JSON
{
  "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_token
  • merchant_id
  • partner_app_install_id
  • environment_grant_id
  • mode
  • granted scope

Step 5: Call Flint APIs With The Install Token

Use the returned access token on the normal Flint public API.

Bash
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:

Bash
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

Bash
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.created
  • partner_app.install.updated
  • partner_app.install.permissions_updated
  • partner_app.install.revoked
  • partner_app.install.environment_grant.created
  • partner_app.install.environment_grant.revoked

Option B: Installed-merchant webhook

Bash
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_types is required
  • mode must be test, live, or both
  • 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_succeeded needs commerce.orders.read
  • payment_intent.succeeded needs payments.payment_intents.read
  • refund.created needs commerce.refunds.read
  • refund.failed needs commerce.refunds.read

Inspect Installs Later

Use these routes from your merchant auth context:

  • GET /v1/developer/partner/apps
  • GET /v1/developer/partner/apps/{partner_app_id}
  • GET /v1/developer/partner/apps/{partner_app_id}/installs
  • GET /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}/revoke
  • POST /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 state value per install attempt
  • validate state on every callback
  • exchange authorization codes only on your backend
  • record merchant_id, partner_app_install_id, environment_grant_id, and mode
  • use webhook deliveries as the durable signal for install lifecycle changes
  • refresh tokens server-side and rotate stored refresh tokens atomically
  • test both test and live installs explicitly

Next Steps