Skip to main content

Documentation Index

Fetch the complete documentation index at: https://help.xoxo.email/llms.txt

Use this file to discover all available pages before exploring further.

If you’re building a product that other people’s XOXO accounts will sign in to — for example, an integration that reads their subscribers or sends emails on their behalf — register an OAuth app and use the authorization code flow.
Building an integration just for your own account? Use a personal API key from Settings → Apps instead — it’s simpler and works the same way.

Request client credentials

OAuth apps are created by the XOXO team. Email hi@xoxo.email with:
  • App name — shown to users on the consent screen
  • Description — one or two sentences about what your app does
  • Redirect URIs — every callback URL your app uses, including local development (e.g. http://localhost:3000/callback)
  • Logo — optional SVG, shown on the consent screen
You’ll get back a client_id and client_secret. Treat the secret like a password.

Endpoints

All OAuth endpoints live on app.xoxo.email.
PurposeMethodURL
AuthorizationGEThttps://app.xoxo.email/oauth/authorize
Token exchangePOSThttps://app.xoxo.email/oauth/token
Token revocationPOSThttps://app.xoxo.email/oauth/revoke

Scopes

XOXO uses a single scope, account, which grants your app the same access the signed-in user has. Request it on every authorization.

Authorization code flow

XOXO supports the authorization code flow with PKCE. Other grant types (client credentials, implicit, password) aren’t supported.
1

Generate a PKCE pair

Create a random code_verifier (43–128 characters) and derive the code_challenge by SHA-256 hashing the verifier and base64url-encoding the result. Only S256 is accepted as the code_challenge_method.
2

Redirect the user to the authorization URL

https://app.xoxo.email/oauth/authorize?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REDIRECT_URI
  &response_type=code
  &scope=account
  &state=RANDOM_STATE
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256
The user signs in (if they aren’t already) and approves access. XOXO redirects back to your redirect_uri with code and state query parameters.
3

Exchange the code for an access token

curl https://app.xoxo.email/oauth/token \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=THE_CODE_FROM_REDIRECT" \
  -d "redirect_uri=YOUR_REDIRECT_URI" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "code_verifier=YOUR_CODE_VERIFIER"
The response includes:
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 7200,
  "refresh_token": "...",
  "scope": "account"
}
4

Call the API

Use the access token as a bearer token against the REST API or MCP server:
curl https://api.xoxo.email/v1/account \
  -H "Authorization: Bearer ACCESS_TOKEN"

Refresh tokens

Access tokens expire after two hours. Exchange the refresh_token for a new access token before it expires:
curl https://app.xoxo.email/oauth/token \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=refresh_token" \
  -d "refresh_token=YOUR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Revoking access

Users can disconnect your app at any time from their Apps settings, which immediately revokes their tokens. To revoke a token from your side — for example, on user logout — call the revocation endpoint:
curl https://app.xoxo.email/oauth/revoke \
  -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "token=ACCESS_OR_REFRESH_TOKEN" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"
Never embed your client_secret in mobile apps, single-page apps, or any code that runs on a user’s device. Keep token exchange on a server you control.