On this page
Get your application credentials
Complete this once for your platform. Each customer authorizes their own connection afterward.
Create and verify your account
Sign up for Lit Circle, verify your email, and open Account → Developer integrations.
Submit your platform for approval
Enter your platform name and exact HTTPS callback origins, such as
https://platform.example.com. Request production and staging origins separately. A Lit Circle admin verifies your identity and control of these origins before approving.Generate credentials after approval
Return to Developer integrations, enter an application name and the complete callback URL, such as
https://platform.example.com/oauth/lit-circle/callback, and confirm your Lit Circle password. Save the generated client ID and one-time client secret in your server’s secret manager.
Register one to ten exact HTTPS callback URLs on approved origins. Wildcards, fragments, and URLs containing user information are rejected. The URL used for authorization and token exchange must match a registered URL exactly. Up to ten integrations may be active per approved partner.
Partner approval gives you credential-management access. Each bookstore or Lit Org still needs OAuth enabled by an admin and consent from an assigned manager. Browser-only applications are not supported.
Connect a bookstore in four steps
Use the authorization-code flow with client-secret authentication and S256 PKCE. Start the connection from a signed-in customer administrator in your platform.
Send the manager to Lit Circle
Create a cryptographically random
stateand a fresh PKCEcode_verifierfor every attempt. Store both in the initiating customer’s server-side session. Redirect the browser toGET /oauth/authorizewith these parameters:https://litcircle.org/oauth/authorize ?response_type=code &client_id=YOUR_CLIENT_ID &redirect_uri=https%3A%2F%2Fplatform.example.com%2Foauth%2Flit-circle%2Fcallback &state=RANDOM_VALUE_BOUND_TO_YOUR_CUSTOMER_SESSION &code_challenge=BASE64URL_SHA256_OF_YOUR_VERIFIER &code_challenge_method=S256The example is wrapped for readability; send one URL with percent-encoded query values. The verifier must be 43–128 characters from
A–Z a–z 0–9 - . _ ~. The challenge is the unpadded base64url SHA-256 digest of the verifier. Sendcode_challenge_method=S256;plainis rejected. Do not send the verifier or client secret in the authorization URL.Handle the approval callback
Lit Circle asks the manager to sign in, choose an eligible bookstore or Lit Org, and approve or cancel. Approval redirects to your callback with
codeandstate. Verify that state matches the initiating session before linking anything. Handleerror=access_deniedas a cancellation. Codes expire after 10 minutes and can be exchanged once.https://platform.example.com/oauth/lit-circle/callback?code=lc_code_...&state=YOUR_ORIGINAL_STATEExchange the code on your server
POST the code, original verifier, client credentials, and exact callback URL as
application/x-www-form-urlencoded. These shell variables refer to values stored on your server:curl --request POST https://litcircle.org/oauth/token \ --data-urlencode 'grant_type=authorization_code' \ --data-urlencode "client_id=$LIT_CIRCLE_CLIENT_ID" \ --data-urlencode "client_secret=$LIT_CIRCLE_CLIENT_SECRET" \ --data-urlencode "code=$LIT_CIRCLE_AUTHORIZATION_CODE" \ --data-urlencode "redirect_uri=$LIT_CIRCLE_CALLBACK_URL" \ --data-urlencode "code_verifier=$LIT_CIRCLE_CODE_VERIFIER"The successful response contains:
{"access_token":"lc_live_...","token_type":"bearer"}HTTP Basic client authentication is also accepted. Tokens are long-lived and opaque; there is no refresh-token grant or
expires_invalue. Never expose the token to browser JavaScript or log credentials.Confirm the entity and save the connection
Call
GET /api/v1/eventswithAuthorization: Bearer YOUR_ACCESS_TOKEN. The response’smeta.subjectcontains the authorized entity’s ID, name, and type. Confirm that selection with your customer, then store the token for that customer and entity. Use a separate connection for each bookstore or Lit Org; never attach a callback to a customer based only on a URL parameter.
Node.js server example
Adapt these functions to your platform’s authenticated routes and durable server-side session store. Configure the three environment variables with your generated credentials and registered callback URL.
Your start and callback routes must enforce customer-administrator access. Derive customerId from the authenticated server context, preserve it through the callback, and atomically claim the attempt before exchanging the code. Render a safe connection error on failure, without logging codes or tokens. These functions illustrate the protocol; your framework supplies session persistence and token storage.
Authorization and callback handlersNode.js
import { createHash, randomBytes } from "node:crypto";
const base = "https://litcircle.org";
const clientId = process.env.LIT_CIRCLE_CLIENT_ID;
const clientSecret = process.env.LIT_CIRCLE_CLIENT_SECRET;
const redirectUri = process.env.LIT_CIRCLE_CALLBACK_URL;
if (!clientId || !clientSecret || !redirectUri) {
throw new Error("Configure Lit Circle credentials and the exact callback URL.");
}
// Call these only for a signed-in customer administrator.
// session is your framework's SERVER-SIDE session, never browser storage.
export function beginAuthorization(session, customerId) {
const state = randomBytes(32).toString("base64url");
const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
session.litCircleOAuth = { state, verifier, customerId, startedAt: Date.now() };
// Persist the session before redirecting the browser to this URL.
const url = new URL("/oauth/authorize", base);
url.search = new URLSearchParams({
response_type: "code", client_id: clientId, redirect_uri: redirectUri,
state, code_challenge: challenge, code_challenge_method: "S256",
}).toString();
return url.toString();
}
export async function completeAuthorization(callbackUrl, session, customerId) {
const url = new URL(callbackUrl);
const pending = session.litCircleOAuth;
if (!pending || pending.customerId !== customerId || !url.searchParams.get("state") ||
url.searchParams.get("state") !== pending.state ||
Date.now() - pending.startedAt > 10 * 60 * 1000) {
throw new Error("Connection attempt expired or state did not match.");
}
delete session.litCircleOAuth;
// Persist removal before continuing; only one callback may claim this attempt.
if (url.searchParams.has("error")) {
throw new Error("Connection was declined or could not be completed.");
}
const code = url.searchParams.get("code");
if (!code) throw new Error("Missing authorization code.");
const response = await fetch(new URL("/oauth/token", base), {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code", client_id: clientId,
client_secret: clientSecret, code, redirect_uri: redirectUri,
code_verifier: pending.verifier,
}),
});
if (!response.ok) throw new Error("Token exchange failed; reconnect.");
const token = await response.json();
if (token.token_type !== "bearer" || !token.access_token) {
throw new Error("Unexpected token response.");
}
const events = await fetch(new URL("/api/v1/events", base), {
headers: { Authorization: "Bearer " + token.access_token },
});
if (!events.ok) throw new Error("Could not identify the authorized entity.");
const { meta } = await events.json();
// Confirm meta.subject with the customer before linking the connection.
// Store the token in server secret storage for this customer's entity.
return { accessToken: token.access_token, subject: meta.subject };
}Use the Events API
The customer’s token is your server’s credential for that entity.
curl https://litcircle.org/api/v1/events \
--header "Authorization: Bearer $LIT_CIRCLE_ACCESS_TOKEN"
Read public associated events with GET. Create events with POST, retain each returned Lit Circle event ID, update complete source-managed snapshots with PUT, and explicitly remove owned events with DELETE. Writes are restricted to the entity’s Event Source; you cannot edit unrelated or manually created events.
V1 has no granular OAuth scopes: a token grants the entity-scoped read and write operations the API exposes. Do not request scope or offline_access. There are no webhooks or incremental-sync endpoints. Follow the API’s snapshot, taxonomy, idempotency, and rate-limit rules.
Disconnect and credential changes
Add a disconnect action in your platform. Revoke the customer’s token on your server, then remove the stored connection and stop syncing:
curl --request POST https://litcircle.org/oauth/revoke \
--data-urlencode "client_id=$LIT_CIRCLE_CLIENT_ID" \
--data-urlencode "client_secret=$LIT_CIRCLE_CLIENT_SECRET" \
--data-urlencode "token=$LIT_CIRCLE_ACCESS_TOKEN"
For valid client credentials, revocation returns 200 even if the token is unknown or already revoked. Disconnecting does not delete events already sent to Lit Circle.
Callback changes
Edit callbacks in Developer integrations. Changes invalidate pending authorization codes, while issued tokens stay connected.
Secret rotation
Rotation immediately invalidates the old secret, pending codes, and all issued tokens for that integration. Update your server’s secret and ask every affected customer to reconnect.
Revoked access
Client or partner revocation stops access. Disabling a subject’s OAuth capability also stops its API access. Reapproving a partner never restores revoked clients; generate a new integration when needed.
Troubleshooting
Show customers a useful error and offer a fresh connection attempt when appropriate.
No eligible bookstore
The manager must be actively assigned to the entity, and a Lit Circle admin must enable its OAuth capability. Archived entities cannot authorize a connection. Your developer’s partner approval does not supply manager access.
Callback mismatch
Compare the complete URL, including path, query, and trailing slash, with the registered callback. An unregistered callback produces a local Lit Circle error; Lit Circle never redirects a code to it.
State mismatch
Reject the callback. Do not exchange the code or bind it to a customer. Preserve the original server-side session through the redirect and start a new attempt.
invalid_requestCheck authorization parameters, the S256 challenge, and form encoding. Missing or malformed PKCE is rejected. Trusted callbacks receive protocol errors through
erroranderror_description.invalid_clientA token-endpoint
401means client authentication failed. Check the client ID and current secret and whether the client or owning partner was revoked.invalid_grantA token-endpoint
400can mean an expired, used, or invalid code; mismatched callback; wrong or missing verifier; or access changed since approval. Start a fresh authorization attempt. A wrong verifier does not consume the code.API
401or403Stop syncing and check whether the token, client, partner, entity capability, or Event Source was revoked or disabled. Offer reconnect when the stored credential is invalid; ask a Lit Circle admin to restore the entity’s OAuth capability when it is disabled.
API
429Wait for
Retry-Afterbefore retrying. Rate limits are shared across the entity’s credentials and grants.
For implementation help, contact Lit Circle. Include the failing endpoint, response status, and the Events API request ID when present. Never include client secrets, authorization codes, verifiers, or access tokens.