Provisioning API
Contents
PostHog's provisioning API lets you create PostHog accounts for your users and deep link them into their PostHog project.
It's intended for partners and platform integrations. If you're integrating your own app with your own PostHog account, you probably want OAuth or a personal API key instead.
Jump to the full Node.js example to see the complete flow in code.
How it works
The flow uses OAuth 2.0 with PKCE (Proof Key for Code Exchange), so there are no shared secrets to manage. Your application is identified by a metadata document hosted on your domain.
Onboarding (once per user)
Deep linking (per click, after onboarding)
Reuses the access token from onboarding to mint a single-use URL that logs the user into their PostHog project.
Set up as a partner
Host a CIMD metadata document
PostHog uses Client ID Metadata Documents (CIMD) for partner registration. There's no signup form to fill out – you host a JSON document at an HTTPS URL on your domain, and that URL becomes your client_id.
Create a JSON file at a stable HTTPS URL, for example https://yourapp.com/.well-known/posthog-client.json:
Requirements:
client_idmust exactly match the URL where this document is hosted.redirect_urisis required and must contain at least one HTTPS URI. This is where PostHog redirects existing users during the consent flow.logo_uri(optional) must be HTTPS if provided.token_endpoint_auth_methodmust be"none"(CIMD clients are public clients, no client secret).- The document must be served with
Content-Type: application/jsonand be under 5 KB. - The URL must use HTTPS, include a path component, and must not contain query parameters or fragments.
PostHog fetches and caches this document automatically.
Subsequent requests reuse the cached version and refresh it in the background based on your Cache-Control: max-age header (clamped between 5 minutes and 24 hours, default 1 hour).
Once your metadata document is live, you can start calling the API. The first request auto-registers your app and returns HTTP 202; retry after a few seconds.
Link your partner app to a PostHog organization (optional)
By default, a CIMD partner app is unverified and capped at 10 account requests per hour. You can link the app to a PostHog organization with a verification token to raise that to 100/hour and surface the partner integration to that org's admins.
In PostHog, go to Organization settings → CIMD verification tokens and click Create token. Copy the
phvt_…value – it's only shown once.Add a
posthog_verification_tokenfield to your CIMD metadata document:JSONThe next time PostHog refreshes the metadata document, the app is linked to the matching organization and the rate limit is bumped.
The token is only used to prove ownership of the partner app – it isn't sent on API requests. You can rotate or revoke a token at any time from the same settings page. Revocation clears the link on the next metadata refresh, so a leaked or stale token can't keep an app linked to your org.
API reference
All endpoints are on https://us.posthog.com (US region) or https://eu.posthog.com (EU region).
Every request must include the API-Version: 0.1d header.
Step 1: Create an account
Create a PostHog account for a user by email. If the user is new, PostHog creates the account and returns an authorization code immediately. If the user already exists, the response tells you to redirect them for consent.
Generate a PKCE code verifier and challenge before making this request:
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Your unique request ID (for idempotency) |
email | string | Yes | User's email address |
name | string | No | User's full name |
client_id | string | Yes | Your CIMD metadata URL |
code_challenge | string | Yes | Base64url-encoded SHA-256 hash of your code verifier (43-128 chars) |
code_challenge_method | string | Yes | Must be "S256" |
scopes | list | No | OAuth scopes to request for the access token. See available scopes. |
configuration.region | string | No | "US" (default) or "EU" |
configuration.organization_name | string | No | Organization name (defaults to "Partner (email)") |
New user response (HTTP 200):
The user receives a welcome email with a link to set their password and access their dashboard.
Existing user response (HTTP 200):
When type is requires_auth, redirect the user to the provided URL. After they approve, PostHog redirects them to your redirect_uris with a code query parameter that you use in step 2.
Step 2: Exchange the code for tokens
Exchange the authorization code for an access token and refresh token. The token endpoint uses standard application/x-www-form-urlencoded encoding.
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | Must be "authorization_code" |
code | string | Yes | The authorization code from step 1 |
code_verifier | string | Yes | The original PKCE code verifier (must match the challenge from step 1) |
Response (HTTP 200):
Authorization codes expire after 5 minutes and can only be used once. Access tokens expire after 1 hour. Use the refresh token to get new tokens.
Token endpoint errors use the standard OAuth 2.0 format:
Step 3: Provision a project
Use the access token to provision a PostHog project and get credentials.
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
service_id | string | No | The plan to provision. "analytics" (default) provisions a standard project. "free" and "pay_as_you_go" set the billing plan explicitly. |
label_prefix | string | No | Label prefix for the personal API key shown in PostHog, up to 25 characters. The key is labeled {label_prefix} - {team_name}. If omitted, empty, or whitespace-only, the key is labeled with just the team name. |
configuration.project_name | string | No | Project name (defaults to "Default project") |
Response (HTTP 200):
Response fields:
| Field | Description |
|---|---|
api_key | The project token (starts with phc_) – use this to initialize PostHog SDKs |
host | The API host (https://us.posthog.com or https://eu.posthog.com) |
personal_api_key | A personal API key scoped to this project – use this for the PostHog API |
Deep linking
For recurring deep links from your application into PostHog – the most common case is an "Open in PostHog" button on a user's connected project – use the deep-link endpoint. Each call returns a short-lived, single-use URL that logs the user into their PostHog project on click. There's no consent screen and no email-mismatch friction: the URL mints a fresh PostHog session for the right user, overriding any other session the browser happens to have.
Authenticate with the access_token you got from Step 2 – the same Bearer credential you use for /resources. The token is scoped to a single team, so the resulting deep link lands the user in the correct project.
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
purpose | string | No | What the deep link should land on. Currently only dashboard is supported. Defaults to dashboard. |
Response:
The returned url is valid for 10 minutes and can only be opened once, so don't pre-render it as the button's href. Wire the button click to your backend, call /deep_links there, then redirect the user to the returned URL.
Requires a trusted partner record
This endpoint is gated on the provisioning_can_issue_deep_links flag on your partner record, which is only enabled for partners admin-onboarded by PostHog. If you get a deep_links_not_enabled 403, ask PostHog to enable it for your CIMD app.
Rotate project credentials
Rotate the project token and create a new personal API key for an existing provisioned project:
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
label_prefix | string | No | Label prefix for the new personal API key shown in PostHog, up to 25 characters. The key is labeled {label_prefix} - {team_name}. If omitted, empty, or whitespace-only, the key is labeled with just the team name. |
The response has the same shape as the project provisioning response and includes the rotated api_key, host, and new personal_api_key.
Refresh tokens
Access tokens expire after 1 hour. Use the refresh token to get new credentials:
Response (HTTP 200):
Each refresh token is single-use. The response includes a new refresh token for subsequent refreshes.
Available scopes
The scopes field in the account request controls what permissions the access token receives. If omitted, a default set of scopes is granted. Available scopes:
| Scope | Description |
|---|---|
customer_journey:read | Read customer journey data |
query:read | Execute read-only queries |
conversation:read | Read PostHog AI conversations |
conversation:write | Create and update PostHog AI conversations |
experiment:read | Read experiments |
feature_flag:read | Read feature flags |
insight:read | Read insights |
organization:read | Read organization details |
person:read | Read person data |
project:read | Read project settings |
ticket:read | Read tickets |
ticket:write | Create and update tickets |
user:read | Read user information |
hog_flow:read | Read Hog flows |
hog_flow:write | Create and update Hog flows |
What the user gets
When you provision a new account, the user receives:
- A welcome email with a link to set their password.
- Full dashboard access at us.posthog.com (or eu.posthog.com for EU).
- The PostHog free tier across all products – no credit card required.
Your integration gets back the project token and host, so you can start sending events the moment the API call returns.
Error handling
Provisioning endpoints (account_requests, resources) return errors in this format:
The token endpoint uses the standard OAuth 2.0 error format instead:
Common error codes:
| Code | HTTP Status | Description |
|---|---|---|
invalid_request | 400 | Missing or invalid field |
unauthorized | 401 | Authentication failed |
forbidden | 403 | Partner not authorized for this action |
expired | 400 | Account request has expired |
invalid_grant | 400 | Authorization code is invalid or expired (token endpoint) |
invalid_label_prefix | 400 | label_prefix is not a string, is longer than 25 characters after trimming, or contains control or Unicode format characters |
invalid_scope | 400 | Unrecognized scope requested |
rate_limited | 429 | Rate limit exceeded |
account_creation_failed | 500 | Server error during account creation |
Rate limits
All provisioning endpoints are rate limited.
CIMD registration (first request from a new client_id):
- 5 requests per minute per IP (burst)
- 10 requests per hour per IP (sustained)
- 100 new client registrations per hour globally
- 5 new client registrations per domain per hour
Account requests (per partner, per hour):
- 10/hour for unverified CIMD partners
- 100/hour for partners linked to a PostHog organization via a verification token
Email team-growth@posthog.com if you need a higher limit for production use.
Full example
Here's a complete example in Node.js:
For the "Open in PostHog" button, call this from your click handler with the user's stored access_token, then redirect the browser to the returned URL: