Skip to main content
The Michelangelo API supports two ways to authenticate, depending on who built the client:
  1. OAuth 2.1 + PKCE — for third-party and community applications acting on behalf of a Michelangelo user. Your app registers itself (no manual signup), sends the user through a consent screen, and receives a token scoped to that user.
  2. Bearer JWT (first-party) — for the official Michelangelo app and first-party clients, which present the user’s existing session token.
Either way, you send the token as a standard bearer token:
All traffic is HTTPS-only. Tokens are validated asymmetrically against the identity provider’s JWKS endpoint — the API verifies signatures locally without calling back to the identity provider on every request.

OAuth 2.1 for Community Apps

Michelangelo’s identity provider is a standard OAuth 2.1 authorization server with PKCE enforced and Dynamic Client Registration enabled — your app registers itself programmatically, with no manual credential request.
Every endpoint you need lives under api.michelangelo.land — auth, OAuth, and data APIs. The infrastructure behind it is an implementation detail: your integration only ever talks to one domain.

Endpoints

The discovery document is the authoritative source — your client should resolve endpoints from it rather than hard-coding them.

1. Register your client

The response contains your client_id. Public clients authenticate at the token endpoint with token_endpoint_auth_method: "none" plus PKCE — there is no client secret.
redirect_uri validation uses exact string matching, including the port. The URI sent to /oauth/authorize and /oauth/token must match a registered URI byte-for-byte.
Native clients with loopback redirects (RFC 8252 — MCP clients like Kimi Code or Claude Code listening on http://127.0.0.1:<ephemeral-port>) work transparently: the platform bridges ephemeral loopback ports through api.michelangelo.land, so you don’t need to register fixed ports. Just register your loopback URI via DCR and use whatever port you listen on — the flow completes normally.

2. Build the authorization URL (PKCE S256)

Generate a high-entropy random code_verifier (43–128 chars), then compute:
Send the user to:
PKCE with the S256 method is mandatory; plain challenges are rejected. The user authenticates and approves (or denies) your app’s request on Michelangelo’s consent screen, hosted at https://api.michelangelo.land/oauth/consent. On approval, the browser is redirected back to your redirect_uri with an authorization code.

4. Exchange the code for a token

The response contains an access_token (a JWT signed with ES256) and a refresh_token. OAuth tokens carry a client_id claim identifying your application — the API uses it for per-client quota and auditing.

5. Call the API and verify

A real response looks like:
If whoami returns 200, your OAuth flow works end to end. When an OAuth flow sends you to Michelangelo’s consent screen, sign in with any method linked to your Michelangelo account:
  • Google or GitHub — one click, the same providers as the app;
  • Email code — a 6-digit code sent to your account email. This works for every account, including Sign in with Apple users, who typically have no password;
  • Email + password — if you set one up.
You don’t create anything new: the consent screen signs into your existing Michelangelo account — the same one you use in the app.

Tokens for Personal Use and Scripts

If you want to call the API yourself (scripts, curl, your own service), you don’t copy a token from a settings page — you run the same OAuth flow above with a client you register via DCR (step 1). Register once with your own redirect_uri (a http://localhost:PORT/callback URI works for local scripts), complete the flow in your browser, and use the resulting access_token and refresh_token. The token identifies both you (user_id) and your client (client_id) — and it works with the MCP server too.

First-Party Bearer Tokens

The official Michelangelo app authenticates users directly and calls the API with the resulting session JWT (Authorization: Bearer <session_jwt>). These tokens are validated the same way (JWKS) but carry no client_id. This mode is reserved for the official app — for anything else, including personal scripts, use the OAuth flow above.

Errors

Authentication failures return a consistent shape:

Beta Notes

The OAuth authorization server is in public beta (Supabase OAuth 2.1). The flow above is verified end to end — DCR, PKCE, consent, token exchange — but check the community channels for GA status and any changes before a production launch.

Next Steps

Async Jobs

Learn the job lifecycle before your first generation.

Examples

Complete curl workflows with a real token.