Migration Guide from API Key
Migrate external partners from API-key reporting to OAuth on the Vibe Developer Platform, with a dual-run period and a 2027-01-01 sunset.
Migrating the Reporting API to OAuth
The Vibe Reporting API is moving from API-key authentication (X-API-Key) to OAuth 2.0 on the Vibe Developer Platform (https://api.vibe.co). This guide walks you through the change.
The good news: the reporting data does not change. Same endpoints, same metrics, same dimensions, same filters. The migration is almost entirely about how you authenticate, plus one new required header and two renamed dimensions.
Whichever way you authenticate, the API key is replaced by an OAuth 2.0 bearer token. Vibe supports two ways to obtain one — choose the one that fits the kind of integration you're building:
- Client credentials — best for private, first-party integrations (you pull your own account's data). You create a
client_id+client_secretin Vibe and exchange it directly for a token. No user, no consent screen. - OAuth authorization code — best for user-facing integrations, where other Vibe users connect their account to your app. The connection is frictionless — users authorize in one click, with no credentials to create or share — and you're granted a token (plus a refresh token to renew it).
Both are valid for reporting.
What's changing — at a glance
| Before (legacy) | After (Developer Platform) | |
|---|---|---|
| Auth | X-API-Key: <key> header | Authorization: Bearer <token> (OAuth 2.0) |
| Getting credentials | An API key per account | An OAuth bearer token, obtained via client credentials or an authorized OAuth app (see below) |
| Required header | — | X-Vibe-Revision: YYYY-MM-DD on every reporting call |
| Dimensions | (see rename table below) | Two dimensions renamed; everything else identical |
| Endpoints, metrics, filters | unchanged | unchanged |
If you only read one thing: swap the X-API-Key header for an Authorization: Bearer OAuth token (obtained via client credentials or an authorized OAuth app), add the X-Vibe-Revision header, and rename two dimensions. That's the whole migration.
Timeline
- Available now — the OAuth reporting endpoints are live. You can migrate today.
- Dual-run window — both
X-API-Keyand OAuth work until the sunset date. - Sunset — 2027-01-01 — on this date,
X-API-Keyauth on the reporting endpoints is disabled and returns401.
Migrate before 2027-01-01 to avoid interruption.
Step 1 — Choose your authentication method
Vibe supports two OAuth methods. Choose based on the integration you're building; both return a bearer token you send the same way.
| Client Credentials | Authorization Code | |
|---|---|---|
| Best for | Private, first-party integrations (you pull your own data) | User-facing integrations (other Vibe users connect their account to your app) |
| Connection | You create a client_id + client_secret in Vibe and use it directly | Users authorize in one click — frictionless, nothing to create or share |
| How you get a token | Exchange the secret at POST /oauth2/token | User authorizes in the browser, then exchange/refresh |
| Consent screen | None | Yes (one-time per user) |
| Token covers | One account (the client's) | One account (the one the user authorizes) |
Not sure which fits? Check with your Vibe contact. The rest of this guide covers both — jump to the section that matches.
Advertiser scope
Advertiser scope is decided by the Vibe user granting access, not by your integration. When they authorize the connection (Authorization Code) or set up the credential (Client Credentials), they choose which advertisers in the account it can pull reporting for:
- Specific advertisers — only the advertiser(s) they select.
- All advertisers — every advertiser currently in the account.
- All advertisers, including future ones — every current advertiser plus any added to the account later, with no need to re-authorize.
Your integration receives whatever was granted — you can't request or change the scope from the API. Reporting requests still pass advertiser_ids (or advertiser_id for purchase events), and those must fall within the granted scope.
Step 2A — Client Credentials
Get a client
You receive a client_id and client_secret for each account, either from your Vibe contact (pre-provisioned) or by creating one yourself in the developer portal (developers.vibe.co) with the reporting:read scope.
One OAuth client maps to one account, exactly like one API key mapped to one account before. If you have many accounts, you'll have many clients.
Get an access token
Exchange your client credentials for a short-lived bearer token. Authenticate with HTTP Basic (client_id as username, client_secret as password):
curl -sX POST https://api.vibe.co/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=reporting:read"Response:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "reporting:read"
}- The
scopeparameter is optional; if omitted you get all scopes available to the client. For reporting you only needreporting:read. - The token endpoint does not require the
X-Vibe-Revisionheader. - Cache and reuse the token for its lifetime (
expires_inseconds, ~1 hour). Do not request a new token on every API call.
Step 2B — Authorization Code
Use this method for user-facing integrations: the user authorizes your app through the browser, and you receive a token scoped to the account they authorize. Each authorization covers one account.
1. Send the user to the authorization endpoint
GET https://api.vibe.co/oauth2/auth
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://your-app.example.com/callback
&response_type=code
&scope=reporting:read
&state=RANDOM_OPAQUE_VALUE
The user is prompted for consent, then redirected back to your redirect_uri with code and state query parameters. Validate that state matches what you sent (CSRF protection).
Trusted partners: consent can be pre-granted so the screen is skipped. This is arranged with Vibe per partner — talk to your Vibe contact.
2. Exchange the code for tokens
curl -sX POST https://api.vibe.co/oauth2/token \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=authorization_code&code=THE_CODE&redirect_uri=https://your-app.example.com/callback"The response includes both an access_token and a refresh_token. Use the refresh token to obtain new access tokens without sending the user through consent again.
Step 3 — Add the X-Vibe-Revision header
X-Vibe-Revision headerEvery reporting request must pin an API revision:
X-Vibe-Revision: 2026-06-01
- The value is a date (
YYYY-MM-DD) matching a published revision. - Pin a fixed revision in production so future backward-incompatible changes don't break you. See the API changelog.
- An unknown or missing revision returns
400 Bad Request. - This header is not required on
/oauth2/tokenor/oauth2/auth.
Step 4 — Rename two dimensions
Two reporting dimensions were renamed on the Developer Platform. Update these names in your report requests; all other dimensions and metrics are identical.
| Legacy dimension | New dimension |
|---|---|
conv_integration_id | event_source_id |
conv_integration_os | event_source_platform |
Step 5 — Call the reporting endpoints
The endpoints themselves are unchanged in shape — only the base URL (https://api.vibe.co) and the headers differ.
Async reports — POST /reports
POST /reportsCreate a report, then poll for completion and download the result.
# 1. Create
curl -sX POST https://api.vibe.co/reports \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Vibe-Revision: 2026-06-01" \
-H "Content-Type: application/json" \
-d '{
"start_date": "2026-06-01",
"end_date": "2026-06-03",
"advertiser_ids": ["<advertiser-uuid>"],
"metrics": ["spend", "impressions", "number_of_purchases"],
"dimensions": ["campaign_id", "campaign_name"],
"granularity": "DAY",
"format": "JSON"
}'
# → 201 { "id": "<report-id>", "status": "CREATED", ... }# 2. Poll (no more than once every 10 seconds)
curl -s https://api.vibe.co/reports/<report-id> \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Vibe-Revision: 2026-06-01"
# → when "status": "READY", a pre-signed "download_url" is includedNotes:
- Dates are
YYYY-MM-DD;start_dateis inclusive andend_dateis exclusive. The range can span at most 45 days, but it can cover any period — including data older than 45 days ago. metricsis required;dimensionsis optional (omit for per-date totals).download_urlis valid for 24 hours after generation.- Reports usually complete in a few minutes. If one stays
CREATED/PROCESSINGpast 30 minutes, resubmit it.
Purchase events — POST /reports/purchases
POST /reports/purchasesA synchronous query for purchase-event detail:
curl -sX POST https://api.vibe.co/reports/purchases \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Vibe-Revision: 2026-06-01" \
-H "Content-Type: application/json" \
-d '{
"advertiser_id": "<advertiser-uuid>",
"start_date": "2026-06-01",
"end_date": "2026-06-03"
}'Before / after
curl -sX POST https://api.vibe.co/reports \
- -H "X-API-Key: $API_KEY" \
+ -H "Authorization: Bearer $ACCESS_TOKEN" \
+ -H "X-Vibe-Revision: 2026-06-01" \
-H "Content-Type: application/json" \
-d '{ ... }'The body is unchanged except for the two renamed dimensions.
Error handling
Reporting endpoints return Vibe's error envelope. A live 401 looks like:
{
"error": {
"type": "token_invalid",
"message": "Authentication required.",
"status": 401,
"request_id": "821a4f0fb5afa6da8df33807ec0f7fd3"
}
}error.detail and error.doc_url are optional and may be absent (as above). Always log request_id — it speeds up support.
| Status | type | Meaning | What to do |
|---|---|---|---|
| 400 | validation | Malformed request or unknown X-Vibe-Revision | Fix the request / pin a valid revision |
| 401 | token_invalid | Token missing, expired, or invalid | Mint a new token and retry |
| 403 | insufficient_scope | Token lacks reporting:read | Request a token with reporting:read |
| 422 | validation | Semantic validation failed; see detail.validation | Fix the listed fields |
| 500 | internal_error | Server error | Retry with exponential backoff |
Verified against the live API: the
401typeistoken_invalid. Treat the othertypevalues above as indicative (they come from the published spec, which already diverges from the live API on the401value) and confirm them against your pinned revision before doing typed error handling.Auth is checked first. An invalid/expired token returns
401even if the request also has a badX-Vibe-Revision— fix auth before debugging the revision header.The token endpoint (
/oauth2/token) returns the standard OAuth error envelope instead — e.g.{ "error": "invalid_client", "error_description": "..." }— so off-the-shelf OAuth libraries parse it correctly.
FAQ
Do I need to change my report request bodies? Only the two renamed dimensions. Metrics, filters, granularity, attribution window, timezone, and date handling are unchanged.
How long do access tokens last? About an hour (expires_in). Cache and reuse; mint a new one when it expires. Don't request one per call.
Can one credential cover all my accounts? No. Each credential is scoped to a single account in both methods — an authorization-code token covers the one account the user authorizes, and a client-credentials client is per account. This matches the old one-key-per-account model. For multiple accounts, you'll have multiple credentials (or authorizations), one per account.
What happens on 2027-01-01? X-API-Key auth on the reporting endpoints stops working and returns 401. Migrate before then.
Where do I get help? Include the request_id from any error response and contact your Vibe support channel.
Updated 6 days ago