Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.manticscore.com/llms.txt

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

ManticScore uses Clerk for identity. Rather than sending your Clerk JWT on every request, you exchange it once for a lightweight session token that the API validates in microseconds. Session tokens expire after 30 minutes, so you’ll refresh them periodically. For zero-friction onboarding, the API also supports anonymous sessions — mint a session from a device id, let users try the product, and rebind their work to a Clerk account when they sign in. This page explains the full authentication lifecycle from login to logout.

How authentication works

Every protected endpoint requires an Authorization header with a Bearer token:
Authorization: Bearer <token>
The API accepts two token types, checked in this order:
  1. Session token — an opaque string returned by POST /auth/session or POST /auth/anonymous. Validated via an O(1) lookup, making it the fastest path. Use this for all normal requests.
  2. Clerk JWT — an RS256-signed JWT from Clerk, verified against the Clerk JWKS. Used only when exchanging for a session token.
Sessions come in two flavors:
  • Authenticated sessions are tied to a Clerk user id (e.g. user_3AtP...) and mint tokens from a Clerk JWT.
  • Anonymous sessions are tied to a device id, require no sign-in, and mint tokens from POST /auth/anonymous. When the user later signs in, call POST /auth/rebind once to re-own every anonymous artifact to the authenticated user.
Streaming endpoints (GET /research/{id}/events, WebSocket /ws, and similar) use the same bearer token. The session TTL is extended automatically when you connect to a stream.

Step 1: Get your Clerk JWT

ManticScore uses Clerk as its identity provider. You get a Clerk JWT from the Clerk dashboard or your Clerk client SDK — not from the ManticScore API directly.
  1. Log in to the Clerk dashboard.
  2. Open your ManticScore application.
  3. Navigate to Users, select your account, and copy a short-lived JWT from the Sessions panel.
Use dashboard JWTs for testing and development. For production applications, generate JWTs programmatically using the Clerk SDK.

Step 2: Exchange the JWT for a session token

Send your Clerk JWT to POST /auth/session. The request body is empty — the JWT goes in the Authorization header.
curl
curl -X POST https://api.manticscore.com/auth/session \
  -H "Authorization: Bearer <your_clerk_jwt>"
Response:
{
  "token": "msc_a1b2c3d4e5f6g7h8...",
  "expires_in": 1800
}
FieldTypeDescription
tokenstringOpaque session token — use this on all subsequent requests
expires_inintegerSeconds until expiry (always 1800 — 30 minutes)
POST /auth/session only accepts a Clerk JWT. Passing an already-issued session token returns a 401. Always authenticate with your original Clerk JWT when requesting a new session.

Step 3: Make authenticated requests

Include the session token as a Bearer token in every request:
curl
curl https://api.manticscore.com/auth/bootstrap \
  -H "Authorization: Bearer msc_a1b2c3d4e5f6g7h8..."
python
import httpx

headers = {"Authorization": f"Bearer {session_token}"}

resp = httpx.get("https://api.manticscore.com/auth/bootstrap", headers=headers)
resp.raise_for_status()
print(resp.json())

Anonymous sessions (zero-friction onboarding)

You can skip the Clerk exchange entirely on first launch and let users try the product without signing in. Call POST /auth/anonymous with a stable device_id and you’ll get back a session token that behaves like any other — attach it on subsequent requests and the API will authorize writes under an anonymous principal.
curl
curl -X POST https://api.manticscore.com/auth/anonymous \
  -H "Content-Type: application/json" \
  -d '{"device_id": "<device_id>"}'
Response:
{
  "token": "ms_sess_a1b2c3d4e5f6...",
  "expires_in": 1800
}
The call is idempotent on device_id — repeated calls for the same device reuse the same anonymous principal, so their work is preserved across launches.
Use a stable, per-device identifier. On iOS, identifierForVendor backed by the Keychain is a good choice. Don’t regenerate a new device_id on every launch or users will see an empty account each time.

Rebind on sign-in

When the user signs in with Clerk, call POST /auth/rebind once with the original device_id and the authenticated session token (or Clerk JWT) in the Authorization header. Every row written under the anonymous principal is re-owned by the Clerk user inside a single database transaction:
curl
curl -X POST https://api.manticscore.com/auth/rebind \
  -H "Authorization: Bearer <authenticated_session_token>" \
  -H "Content-Type: application/json" \
  -d '{"device_id": "<device_id>"}'
Response:
{
  "rebound": true,
  "rows_updated": 14,
  "anon_uuid": "b1e4..."
}
Rebind is safe to call defensively after every sign-in:
  • If the device never used an anonymous session, the call is a no-op and returns {"rebound": false}.
  • If the same device and user have already been rebound, the call is also a no-op.
  • If the device was previously rebound to a different Clerk user, the call returns 409 Conflict.
Rebind requires an authenticated token. Calling it with an anonymous session token returns 401 "Rebind requires an authenticated user token; got anonymous session". Always sign the user in first, then call rebind with the authenticated token.
For the full parameter and error reference, see POST /auth/anonymous and POST /auth/rebind in the API reference.
After getting a session token, call GET /auth/bootstrap as your first request. This endpoint returns your full profile, subscription details, and connected secret status in one call — saving you multiple round trips.
curl
curl https://api.manticscore.com/auth/bootstrap \
  -H "Authorization: Bearer <session_token>"
Response:
{
  "profile": {
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "plan": "free",
    "credits_used": 3,
    "credits_total": 20,
    "project_count": 5
  },
  "subscription": {
    "plan": "free",
    "credits_used": 3,
    "credits_total": 20
  },
  "secrets": {
    "has_github_pat": true,
    "has_anthropic_key": false,
    "has_openai_key": false
  }
}
The bootstrap endpoint also auto-creates your ManticScore profile on first access, so you don’t need a separate account setup step.

Session token TTL and refreshing

Session tokens expire after 30 minutes. Plan to refresh proactively rather than waiting for a 401.
Session tokens use a sliding TTL — connecting to a streaming endpoint extends the TTL automatically. For non-streaming usage, re-authenticate by calling POST /auth/session again with your Clerk JWT before the token expires:
curl
# Re-authenticate with your Clerk JWT to get a fresh session token
curl -X POST https://api.manticscore.com/auth/session \
  -H "Authorization: Bearer <your_clerk_jwt>"
python
import time
import httpx

CLERK_JWT = "<your_clerk_jwt>"
BASE_URL = "https://api.manticscore.com"


class ManticScoreClient:
    def __init__(self):
        self._token = None
        self._expires_at = 0

    def _get_token(self) -> str:
        # Refresh 60 seconds before expiry
        if time.time() >= self._expires_at - 60:
            resp = httpx.post(
                f"{BASE_URL}/auth/session",
                headers={"Authorization": f"Bearer {CLERK_JWT}"},
            )
            resp.raise_for_status()
            data = resp.json()
            self._token = data["token"]
            self._expires_at = time.time() + data["expires_in"]
        return self._token

    def get(self, path: str, **kwargs):
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {self._get_token()}"
        return httpx.get(f"{BASE_URL}{path}", headers=headers, **kwargs)

    def post(self, path: str, **kwargs):
        headers = kwargs.pop("headers", {})
        headers["Authorization"] = f"Bearer {self._get_token()}"
        return httpx.post(f"{BASE_URL}{path}", headers=headers, **kwargs)

Logging out

To revoke the current session token, send a DELETE /auth/session request:
curl
curl -X DELETE https://api.manticscore.com/auth/session \
  -H "Authorization: Bearer <session_token>"
Response:
{
  "success": true
}
The token is immediately invalidated. Any subsequent requests using that token will receive a 401.

Error responses

All auth errors return 4xx JSON responses with a detail field.
StatusdetailCause
401"Missing Bearer token"Authorization header is absent
401"Token expired"Session token TTL has elapsed
401"Invalid token: ..."Token is malformed or unrecognized
401"Invalid authorized party"JWT azp claim doesn’t match the expected value
401"Rebind requires an authenticated user token; got anonymous session"POST /auth/rebind was called with an anonymous session token — sign in first, then retry with the authenticated token
409"device <id> already rebound to a different user"POST /auth/rebind — the device has already been rebound to a different Clerk user
422"device_id required"POST /auth/anonymous or POST /auth/rebind was called without device_id in the body
503"JWKS not loaded"Clerk JWKS endpoint is temporarily unreachable
Example error response:
{
  "detail": "Token expired"
}
Handle 401 responses by re-authenticating with your Clerk JWT and retrying the original request. A 503 is transient — retry with backoff.
python
import httpx

def request_with_auth_retry(client: ManticScoreClient, path: str):
    resp = client.get(path)
    if resp.status_code == 401:
        # Force a token refresh on next call
        client._expires_at = 0
        resp = client.get(path)
    resp.raise_for_status()
    return resp.json()

Summary

1

Get a Clerk JWT

Retrieve your JWT from the Clerk dashboard or your Clerk client SDK.
2

Exchange for a session token

POST /auth/session with your Clerk JWT in the Authorization header. Save the returned token.
3

Call bootstrap

GET /auth/bootstrap to load your profile and verify the token works end-to-end.
4

Make requests

Pass Authorization: Bearer <session_token> on every subsequent API call.
5

Refresh before expiry

Session tokens last 30 minutes. Re-authenticate with your Clerk JWT before they expire.
6

Logout when done

DELETE /auth/session to revoke the token immediately.