Skip to main content

Authorize a user (OAuth)

Use the authorization code flow with PKCE when your app acts on behalf of an Vantr merchant — for example, a third-party app that an account owner connects to their store. The merchant signs in on Vantr, approves the scopes you request, and your app receives a tenant-bound access token. You never see the merchant’s password, and each merchant authorizes you separately.
If you are calling your own Vantr account from a backend you control, you do not need this flow. Use client credentials instead — it is a single request with no browser redirect.

Which flow do I need?

Authorization code + PKCE

A third party connects other merchants’ Vantr accounts to your app. Requires a browser redirect and a per-merchant consent screen. This guide.

Client credentials

You call your own account from a trusted server. No browser, no user. See Authentication.

How the flow works

1

You redirect the merchant to Vantr

With your client_id, requested scope, and a PKCE challenge.
2

The merchant signs in and approves

Vantr shows a consent screen listing exactly the scopes you asked for.
3

Vantr redirects back with a one-time code

To your registered redirect_uri, with ?code=…&state=….
4

You exchange the code for tokens

Server-side, at /oauth/token, proving you started the request with your PKCE verifier.
5

You call the v2 API with the access token

The token is bound to that merchant’s tenant. Refresh it when it expires.

Before you start

Register a developer application

Create an app in the developer portal. Add every redirect URI you will use under Redirect URIs — the callback must match one of them exactly (scheme, host, port, path, and trailing slash). Enable the authorization_code grant type and select the scopes your app may request.

Decide: confidential or public client

A confidential client runs entirely on a server and can keep a client_secret. A public client (single-page app, mobile, desktop, CLI) cannot. PKCE is required either way; the difference is only how you authenticate at the token endpoint (see Step 4).
Always run the token exchange server-side and keep the client_secret there. The browser only ever sees the authorize redirect and the code — never the secret or the access token, for a confidential client.

Step 1 — Generate a PKCE verifier and challenge

For each authorization attempt, generate a fresh random code verifier (43–128 characters from A–Z a–z 0–9 - . _ ~) and derive its challenge as BASE64URL(SHA256(verifier)). Store the verifier in the user’s session — you will need it in Step 4.
Only S256 is supported. A plain challenge or a verifier shorter than 43 characters is rejected with invalid_grant.

Step 2 — Send the merchant to the authorize URL

Redirect the user’s browser to https://api.vantr.ai/oauth/authorize with the query parameters below. Vantr forwards the merchant to its consent screen (they sign in if needed).
string
required
Must be code.
string
required
Your application’s client ID.
string
required
Must exactly match a redirect URI registered on your application.
string
required
Space-separated scopes, e.g. products.read categories.read. Request only what you need — see Scopes.
string
required
The PKCE challenge from Step 1.
string
required
Must be S256.
string
Strongly recommended. An opaque value you generate and verify on return to prevent CSRF. Vantr echoes it back unchanged.
The merchant approving the consent screen must have permission for the scopes you request. If their role cannot grant a scope, the request is denied — request the narrowest scope set that does the job.

Step 3 — Handle the redirect back

After the merchant approves, Vantr redirects the browser to your redirect_uri with a one-time code and your state. Always verify state against the value you stored, then continue server-side.
Approved
If the merchant declines (or cannot grant a scope), you receive an error instead of a code:
Denied
Node.js callback
The authorization code is single-use and expires 5 minutes after it is issued. Exchange it immediately and never log it.

Step 4 — Exchange the code for tokens

POST to https://api.vantr.ai/oauth/token with Content-Type: application/x-www-form-urlencoded.
string
required
authorization_code.
string
required
The code from Step 3.
string
required
The same redirect_uri you used in Step 2.
string
required
The PKCE verifier from Step 1 that matches the challenge you sent.
How you authenticate the client depends on its type:
Send your credentials with HTTP Basic auth (client_id as username, client_secret as password).
cURL
Node.js
A successful response returns the token set:
Response
string
required
Opaque bearer token for v2 requests. Lives 1 hour by default.
string
Use it to mint a new access token without sending the merchant through consent again. Lives 90 days. Omitted if the application has refresh tokens disabled.
string
required
The scopes actually granted. May be narrower than you requested.
Store the refresh_token (and the granted scope) per merchant in encrypted, server-side storage, keyed by tenant. Treat it like a password.

Step 5 — Call the v2 API

Send the access token as a Bearer credential. The token already identifies the merchant’s tenant — you never pass a tenant ID yourself.
cURL
Node.js
A 200 here confirms the full chain works: PKCE, consent, tenant binding, and scope enforcement.

Step 6 — Refresh the access token

When the access token expires (after ~1 hour), exchange the refresh token for a new one — same endpoint, grant_type=refresh_token. Use the same client authentication as Step 4.
cURL
You may pass scope to narrow the new token, but never to widen it beyond the original grant.
Refresh tokens rotate. Each refresh revokes the token you just used and returns a new refresh_token — persist the new one and discard the old. Replaying a spent refresh token fails with invalid_grant; treat that as a signal the token may have leaked and re-authorize the merchant.

Token lifetimes

Revoke and inspect

Revoke
When a merchant disconnects your app, call /oauth/revoke so their tokens stop working immediately rather than waiting for expiry.

Troubleshooting

The code_verifier you sent in Step 4 does not hash to the code_challenge you sent in Step 2. Make sure you store the verifier per attempt and that both sides use base64url without padding.
The redirect_uri in the token request must be byte-for-byte identical to the one in the authorize request, and both must be registered on the application. Watch for trailing slashes and http vs https.
Authorization codes are single-use and expire after 5 minutes. Do not retry an exchange with the same code; restart from Step 2.
A confidential client must use HTTP Basic auth, and the secret must not have been rotated. A public client must send client_id in the body and must be registered as public.
The merchant declined, or their role cannot grant a scope you requested. Request fewer or narrower scopes, or have an account owner complete the connection.
The token is valid but its granted scopes do not include one the endpoint requires. Check the scope on the endpoint’s API reference page and re-authorize with it included.
OAuth token calls are rate limited to 60 per minute per credential. Back off using the Retry-After header. v2 endpoints allow 240 per minute.

Security checklist

One verifier per attempt

Generate a fresh code_verifier and state for every authorization and bind them to the user’s session.

Verify state

Reject the callback if state does not match what you stored.

Exchange server-side

Never expose the client_secret or run the token exchange in the browser for a confidential client.

Encrypt refresh tokens

Store refresh tokens encrypted, per tenant, and rotate the stored value on every refresh.
Building a server-only integration with no merchant in the loop? Skip all of this and use client credentials.