Beta

Developer Apps (OAuth2)

Let users authorize your app to access Bolta on their behalf. Instead of asking users for an API key, redirect them to Bolta, get their consent, and exchange the returned code for a token that works everywhere an API key does.

How it works

UserYour AppBoltaClick "Connect with Bolta"Redirect to /oauth/authorizeShow consent screenUser approvesRedirect with ?code=…POST /oauth/token (exchange)Return access_tokenAPI calls with access_token
OAuth2 Authorization Code flow

1. Register your app

Go to Settings → Developer Apps and create a new application. You'll be asked for:

  • App name — shown on the consent screen.
  • Description — explains what your app does.
  • Redirect URL — where Bolta sends users after approval.
  • Scopes — the permissions your app needs.

On creation you receive a Client ID and Client Secret. The secret is shown once — store it somewhere safe.

2. Redirect users to authorize

When a user wants to connect their Bolta account to your app, redirect them to:

text
https://bolta.ai/oauth/authorize?client_id={CLIENT_ID}&response_type=code&redirect_uri={REDIRECT_URI}&scope={SCOPES}&state={STATE}
client_id
Yes

Your app's Client ID.

response_type
Yes

Must be code.

redirect_uri
Yes

Must match a registered redirect URL exactly.

scope
No

Space-separated list of scopes. Defaults to the app's configured scopes.

state
No

Random string echoed back in the callback. Required for CSRF protection.

3. Handle the callback

After the user approves or denies, Bolta redirects to your registered URL with query parameters.

Approved

text
https://yourapp.com/callback?code=abc123&state=xyz

Denied

text
https://yourapp.com/callback?error=access_denied&state=xyz

Always verify the state parameter matches what you sent. The authorization code is single-use and expires after 10 minutes.

4. Exchange code for token

Make a server-side POST request — never from the browser, since it carries your client secret.

bash
curl -X POST https://platty.boltathread.com/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "abc123",
    "redirect_uri": "https://yourapp.com/callback",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET"
  }'

Response

json
{
  "access_token": "...",
  "refresh_token": "...",
  "token_type": "bearer",
  "expires_in": 5184000,
  "scope": "posts:read posts:write accounts:read",
  "workspace_id": "...",
  "user_id": "..."
}

5. Make API calls

Use the access token as a Bearer token — it works on every /api/v1/ endpoint and the MCP server.

bash
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  "https://platty.boltathread.com/api/v1/workspaces/"

Token lifecycle

  • Code
    Authorization codes are single-use and expire after 10 minutes.
  • Access
    Access tokens last 60 days. Refresh them with the refresh token before expiry.
  • Refresh
    Send grant_type=refresh_token to /oauth/token to get a new access token. Refresh tokens rotate on use.
  • Revoke
    Users can revoke any app at any time from Settings → Approved Apps. Revoked tokens fail with 401.

Errors

invalid_client
Token exchange

Client ID or Client Secret is wrong.

invalid_grant
Token exchange

Code is invalid, expired, or already used.

unsupported_grant_type
Token exchange

grant_type is not authorization_code or refresh_token.

access_denied
Callback

User denied the authorization request.

invalid_scope
Authorize

Requested scope is unknown or not granted to the client.

Full example (Node.js)

typescript
import express from "express";
import crypto from "crypto";

const app = express();
const CLIENT_ID = process.env.BOLTA_CLIENT_ID!;
const CLIENT_SECRET = process.env.BOLTA_CLIENT_SECRET!;
const REDIRECT_URI = "https://yourapp.com/callback";

app.get("/connect", (req, res) => {
  const state = crypto.randomBytes(16).toString("hex");
  req.session.oauthState = state;
  const params = new URLSearchParams({
    client_id: CLIENT_ID,
    response_type: "code",
    redirect_uri: REDIRECT_URI,
    scope: "posts:read posts:write accounts:read",
    state,
  });
  res.redirect("https://bolta.ai/oauth/authorize?" + params);
});

app.get("/callback", async (req, res) => {
  const { code, state, error } = req.query;
  if (error) return res.status(400).send("Authorization denied");
  if (state !== req.session.oauthState) return res.status(403).send("Bad state");

  const r = await fetch("https://platty.boltathread.com/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      grant_type: "authorization_code",
      code,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
    }),
  });
  const { access_token, refresh_token, workspace_id } = await r.json();
  // Store these somewhere durable, scoped to the user.
  res.json({ connected: true, workspace_id });
});
Bolta — Create a Week of Social Media Content in Minutes