Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.maxcare.ai/llms.txt

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

If your app runs outside the Max AI dashboard (a standalone web app, mobile app, or portal), you need a way to identify which Max AI user is logged in. The OAuth 2.0 Authorization Code flow lets users sign in with their Max AI account and share their identity with your app.
Embedded apps (running inside the Max AI iframe) don’t need OAuth — they receive user context automatically via the App Bridge. OAuth is only for standalone/external apps.

Prerequisites

Before setting up OAuth, you need:
  1. A developer accountGet started if you haven’t already
  2. A marketplace app — Create one in the developer console or via the CLI
  3. An API key — Generate one from the developer console under your app’s API Keys tab (see Authentication)
Your app’s slug (used as client_id in OAuth) is visible in the developer console on your app’s settings page.

How It Works

Redirect to Max AI

Your app sends the user to /oauth/authorize with your client_id, redirect_uri, and a random state nonce for CSRF protection.

User authenticates and consents

Max AI handles login (via Clerk), then shows a consent screen where the user reviews your app’s permissions and selects which organizations to authorize.

Redirect back with code

Max AI redirects the user to your redirect_uri with a short-lived authorization code and the original state parameter.

Exchange code for user info

Your backend calls POST /v3/oauth/token with the authorization code and your API key. This is a server-to-server call — the code is never exposed to the browser.

Receive identity

Max AI returns an OIDC id_token (ES256 JWT), user profile, and the list of authorized organizations with their facilities.

Setup

1. Register Redirect URIs

Add your callback URLs to the [oauth] section of your app manifest (max-ai.app.toml):
[oauth]
redirect_uris = [
  "https://myapp.com/auth/callback",
  "http://localhost:3000/auth/callback"
]
Or configure them in the developer console under your app version’s OAuth tab.
Redirect URIs must exactly match — including scheme, host, port, and path. No wildcards. localhost is allowed for development.

2. Redirect to Authorize

When a user wants to sign in with Max AI, redirect them to the authorize endpoint:
https://app.maxcare.ai/oauth/authorize
  ?client_id=your-app-slug
  &redirect_uri=https://myapp.com/auth/callback
  &state=random-csrf-nonce
  &response_type=code
ParameterRequiredDescription
client_idYesYour app’s slug (from the developer console)
redirect_uriYesMust exactly match a registered redirect URI
stateYesRandom string for CSRF protection — you must verify it matches on callback
response_typeNoMust be code (default)
promptNoSet to consent to force the consent screen even if the user previously authorized
The user will see a consent screen showing your app’s name, the permissions it requires, and which organizations to authorize.

3. Handle the Callback

After the user approves (or denies), Max AI redirects to your redirect_uri: On approve:
https://myapp.com/auth/callback?code=abc123def456&state=random-csrf-nonce
On deny:
https://myapp.com/auth/callback?error=access_denied&state=random-csrf-nonce
Always verify that the returned state matches the one you sent. This prevents CSRF attacks.
Authorization codes expire after 10 minutes and can only be used once. Exchange them immediately.

4. Exchange the Code

Your backend exchanges the authorization code for user info by calling the token endpoint. Authenticate with your app’s API key:
curl -X POST "https://api.maxcare.ai/v3/oauth/token" \
  -H "Authorization: Bearer max_prd_ak_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "abc123def456",
    "redirect_uri": "https://myapp.com/auth/callback"
  }'
The token endpoint does not require the X-Organization-Id header. The authorization code already encodes which organizations the user authorized.

5. Receive User Info

The token endpoint returns the user’s identity, authorized organizations, and an OIDC-compliant id_token:
{
  "id_token": "eyJhbGciOiJFUzI1NiIs...",
  "user": {
    "id": "usr_1231b6f32b4f4b8f8eeb4f7806bc45b0",
    "email": "jane@clinic.com",
    "firstName": "Jane",
    "lastName": "Doe",
    "imageUrl": "https://img.clerk.com/..."
  },
  "authorizedOrganizations": [
    {
      "id": "org_7e2c8cfeb7a94deb986de7012589e72b",
      "name": "Dermatology Clinic",
      "role": "admin",
      "facilities": [
        {
          "id": "fac_a1b2c3d4...",
          "name": "Main Office",
          "address": "123 Main St, Austin, TX 78701"
        }
      ]
    }
  ]
}
FieldDescription
id_tokenOIDC-compliant JWT (ES256-signed). Verify via the JWKS endpoint.
userThe authenticated Max AI user
authorizedOrganizationsOrganizations the user selected, with their role and facilities

6. Make API Calls

Now that you know the user and their organizations, use the organization IDs as the X-Organization-Id header when calling the Public API:
curl -X GET "https://api.maxcare.ai/v3/patients" \
  -H "Authorization: Bearer max_prd_ak_YOUR_API_KEY" \
  -H "X-Organization-Id: 7e2c8cfe-b7a9-4deb-986d-e7012589e72b"
The X-Organization-Id must be one of the organization IDs returned in authorizedOrganizations. Your API key provides data access; the OAuth flow provides user identity.

Code Example

const crypto = require("crypto");

// Step 1: Redirect to Max AI
app.get("/auth/login", (req, res) => {
  const state = crypto.randomBytes(16).toString("hex");
  req.session.oauthState = state;

  const params = new URLSearchParams({
    client_id: "your-app-slug",
    redirect_uri: "https://myapp.com/auth/callback",
    state,
    response_type: "code",
  });

  res.redirect(`https://app.maxcare.ai/oauth/authorize?${params}`);
});

// Step 2: Handle callback
app.get("/auth/callback", async (req, res) => {
  const { code, state, error } = req.query;

  if (error) return res.redirect(`/login?error=${error}`);
  if (state !== req.session.oauthState) return res.status(403).send("Invalid state");

  // Step 3: Exchange code for user info
  const response = await fetch("https://api.maxcare.ai/v3/oauth/token", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.MAXAI_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://myapp.com/auth/callback",
    }),
  });

  const data = await response.json();

  if (data.error) return res.redirect(`/login?error=${data.error}`);

  // Step 4: Create your app's session
  req.session.user = data.user;
  req.session.organizations = data.authorizedOrganizations;
  res.redirect("/dashboard");
});

Token Exchange Errors

The token endpoint returns OAuth 2.0 spec-compliant errors:
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
errorHTTP StatusDescription
invalid_request400Missing required parameter (grant_type, code, or redirect_uri)
unsupported_grant_type400Only authorization_code is supported
invalid_grant400Code is invalid, expired, already used, or redirect_uri doesn’t match
invalid_client401API key is missing, invalid, expired, or revoked
server_error500OIDC signing is not configured on the server

Repeat Authorization

When a user authorizes your app, the grant is remembered. On subsequent OAuth flows:
  • Same permissions — the user is redirected back instantly (no consent screen)
  • Permissions changed — the consent screen is shown again
  • Force re-consent — add prompt=consent to the authorize URL to let users change which organizations they share

OpenID Connect

The id_token is a standard OIDC JWT signed with ES256. You can verify it using the public key from the JWKS endpoint:
EndpointURL
OIDC DiscoveryGET /.well-known/openid-configuration
JWKSGET /.well-known/jwks.json
The id_token contains standard OIDC claims:
ClaimDescription
issIssuer URL (e.g., https://app.maxcare.ai)
subUser ID
audYour app’s slug
iat / expIssued at / expires at (1 hour)
emailUser’s email
given_nameFirst name
family_nameLast name
pictureProfile image URL

Embedded vs External Apps

Embedded (iframe)External (standalone)
Runs insideMax AI dashboardYour own domain
User contextApp Bridge SDKOAuth flow
Auth methodSession token (automatic)API key + OAuth code exchange
SetupSet appUrl in manifestSet redirect_uris in manifest
Use whenApp is part of the clinic workflowApp has its own portal or mobile app
You can support both — use the App Bridge when embedded, and OAuth when accessed standalone.