Authenticate with OAuth 2.0 Authorization Code
Create a Vibe app and use the OAuth 2.0 Authorization Code flow to access a Vibe user's account.
Creating an App
- In the Vibe Developer tool, create an app and select OAuth as the authentication method.
- Register every redirect URI your app uses. Each URI must exactly match the
redirect_uriin your authorization requests; register separate development and production URIs when needed. - Select only the grant types and scopes your integration requires.
- After app creation, copy the
client_idandclient_secret. Store the secret only on your server; it is required for the token exchanges below.
For Marketplace requirements—including app information, required listing content, support materials, scope review, and submission—see Submit your app for review and Marketplace listing.
Base URL
All OAuth endpoints are available at:
https://api.vibe.co
Developer note — Pick the right grantFor server-to-server integrations with no end user, such as cron jobs, exports, and backends, use
client_credentials.When a Vibe user is signing into your app and granting your app access to their accounts, use
authorization_code.Don't use
client_credentialsto act on a user's account.
Authorization Code Flow
Use this flow when your application needs access to resources owned by a specific Vibe user. The user is redirected to Vibe to authenticate and approve access, then returned to your app with an authorization code you exchange for a token.
Step 1 — Generate state and a PKCE pair
state and a PKCE pairBefore each authorization request, generate three new values:
state: an opaque, cryptographically random value that protects against CSRF. You will verify it on the callback in Step 3.code_verifier: a cryptographically random string of 43 to 128 characters fromA-Z,a-z,0-9,-,.,_,~.code_challenge:BASE64URL(SHA256(code_verifier)), without=padding.
import crypto from 'node:crypto';
const state = crypto.randomBytes(16).toString('base64url');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');import base64
import hashlib
import secrets
state = secrets.token_urlsafe(16)
code_verifier = secrets.token_urlsafe(32)
code_challenge = (
base64.urlsafe_b64encode(hashlib.sha256(code_verifier.encode("ascii")).digest())
.rstrip(b"=")
.decode("ascii")
)Store state and code_verifier server-side, tied to the user's session. You will send state and code_challenge in Step 2, verify state in Step 3, and send code_verifier in Step 4.
Never reuse these values across authorization requests, and never send the code_verifier in the authorization request.
Step 2 — Redirect the user to Vibe
Send the user to the Vibe authorization endpoint with the following query parameters:
GET https://api.vibe.co/oauth2/auth
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | Your application's client ID |
redirect_uri | Yes | URL to redirect the user after authorization. Must match a registered redirect URI for your app. |
response_type | Yes | Must be code |
scope | Yes | Space-separated list of scopes to request (e.g. campaigns:read) |
state | Yes | An opaque value you generate to prevent CSRF. You must verify this on the callback. |
code_challenge | Yes | PKCE code challenge: the Base64URL-encoded (no padding) SHA-256 hash of your code_verifier. |
code_challenge_method | Yes | Must be S256. |
Example redirect URL:
https://api.vibe.co/oauth2/auth?client_id=<YOUR_CLIENT_ID>&redirect_uri=https%3A%2F%2Fyourapp.com%2Fcallback&response_type=code&scope=campaigns%3Aread&state=abc123xyz&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256
Step 3 — Handle the callback
After the user authorizes your app, Vibe redirects them to your redirect_uri with two query parameters:
GET https://yourapp.com/callback?code=<AUTHORIZATION_CODE>&state=<YOUR_STATE>
Before proceeding, verify that state matches the value you stored in Step 1. If it does not match, reject the request.
If the user denies access or an error occurs, the redirect will include error and error_description parameters instead:
GET https://yourapp.com/callback?error=access_denied&error_description=The+user+denied+access
Step 4 — Exchange the code for a token
Make a server-side POST request to exchange the authorization code for an access token. Never make this request from client-side code, as it requires your client secret.
POST https://api.vibe.co/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <BASE64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)>
grant_type=authorization_code
&code=<AUTHORIZATION_CODE>
&redirect_uri=https://yourapp.com/callback
&code_verifier=<YOUR_CODE_VERIFIER>
The Authorization header value is Basic followed by the Base64 encoding of <client_id>:<client_secret>.
code_verifier is the original value you generated in Step 1, before hashing. If it does not match the code_challenge sent in Step 2, the request fails with invalid_grant.
Example response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "ory_rt_...",
"token_type": "Bearer",
"expires_in": 1800
}Store the refresh_token securely. You will use it to obtain new access tokens without requiring the user to re-authorize. The access token expires after 30 minutes.
Step 5 — Call the API
Include the access token as a Bearer token in the Authorization header of every API request:
GET https://api.vibe.co/*
Authorization: Bearer <ACCESS_TOKEN>
Token Response Reference
| Field | Type | Description |
|---|---|---|
access_token | string | JWT token to use in API requests. TTL 30 minutes. |
refresh_token | string | Token used to obtain a new access token. Only present in the Authorization Code flow. TTL 90 days. |
token_type | string | Always Bearer |
expires_in | integer | Access token lifetime in seconds |
Access tokens are JWTs. You can inspect the payload by Base64-decoding the middle segment (between the two . characters). The payload contains standard claims such as sub, scope, iat, and exp. Do not rely on the token payload structure in your integration logic — use the API responses instead.
Tokens expire after the duration indicated by expires_in. Use the refresh_token from the Authorization Code flow to obtain a new access token without user interaction. The Client Credentials flow does not issue refresh tokens.
Refreshing Access Tokens
When an access token expires, use the refresh token to obtain a new one without requiring the user to re-authorize. This only applies to the Authorization Code flow — Client Credentials tokens cannot be refreshed.
POST https://api.vibe.co/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <BASE64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)>
grant_type=refresh_token
&refresh_token=<YOUR_REFRESH_TOKEN>
Example response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "ory_rt_...",
"token_type": "Bearer",
"expires_in": 1800
}Each refresh issues a new refresh token. Replace the stored refresh token with the one returned in the response — the previous token is invalidated.
Error Reference
Errors from the authorization endpoint and token endpoint follow OAuth 2.0 standard error codes.
Gotcha — OAuth error envelope
400and401responses from OAuth endpoints follow RFC 6749 §5.2:{'{'} "error": "invalid_grant", "error_description": "..." {'}'}.They do not use the Vibe error envelope. Parse both shapes in code that handles OAuth and the rest of the Vibe API.
| Error | Description |
|---|---|
invalid_client | Client authentication failed. Check your client_id and client_secret. |
invalid_grant | The authorization code is invalid, expired, or already used, or the code_verifier does not match the code_challenge. |
invalid_request | A required parameter is missing or malformed, including a missing code_challenge or a code_challenge_method other than S256. |
invalid_scope | One or more of the requested scopes are invalid or not permitted for your client. |
access_denied | The user denied the authorization request. |
unsupported_grant_type | The requested grant type is not supported. |
Error responses from the token endpoint are JSON:
{
"error": "invalid_client",
"error_description": "Client authentication failed."
}Updated 18 days ago