{
  "components": {
    "parameters": {
      "ConnectedAppId": {
        "description": "The app's `id` from the connected app list",
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "maxLength": 64,
          "type": "string"
        }
      },
      "SessionId": {
        "description": "The session's `id` from the session list",
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "maxLength": 64,
          "type": "string"
        }
      }
    },
    "responses": {
      "ForbiddenError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#forbidden",
              "error": "forbidden",
              "message": "The access token lacks the scope sessions:read"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "The access token lacks the operation's scope",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer error=\"insufficient_scope\", scope=\"\u003cthe scope\u003e\"` (RFC 6750)",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "InternalError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#internal_error",
              "error": "internal_error",
              "message": "Failed to list sessions"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Something failed on Authalla's side"
      },
      "NotFoundError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#not_found",
              "error": "not_found",
              "message": "Session not found"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "The user has no such session or connected app"
      },
      "UnauthorizedError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#unauthorized",
              "error": "unauthorized",
              "message": "The access token is invalid, expired or revoked"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Missing, invalid, expired or revoked access token, or one this API doesn't accept",
        "headers": {
          "WWW-Authenticate": {
            "description": "`Bearer error=\"invalid_token\"`, or `Bearer` alone when no token was sent (RFC 6750)",
            "schema": {
              "type": "string"
            }
          }
        }
      },
      "ValidationError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#validation_error",
              "error": "validation_error",
              "message": "name cannot be empty"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "The request doesn't match the spec"
      }
    },
    "schemas": {
      "ConnectedApp": {
        "additionalProperties": false,
        "description": "An app that holds a remembered consent or a live refresh token for the user",
        "properties": {
          "client_id": {
            "description": "The OAuth2 client_id the app signs in with (a URL for an app that registered itself)",
            "type": "string"
          },
          "current": {
            "description": "This is the app calling",
            "type": "boolean"
          },
          "id": {
            "description": "The app's ID; revoke it with this",
            "type": "string"
          },
          "last_used_at": {
            "description": "When the app was last issued a token for the user, at sign-in or refresh. Its calls to the resource server aren't seen by Authalla.",
            "format": "date-time",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "resource_servers": {
            "description": "The resource servers the app's live refresh tokens are for, such as an MCP server",
            "items": {
              "$ref": "#/components/schemas/ConnectedAppResourceServer"
            },
            "type": "array"
          },
          "scopes": {
            "description": "The scopes of the app's live tokens and remembered consent",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "self_registered": {
            "description": "The app registered itself with a Client ID Metadata Document, such as Claude or ChatGPT, rather than being added by the tenant's administrator",
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "client_id",
          "name",
          "self_registered",
          "resource_servers",
          "scopes",
          "current"
        ],
        "type": "object"
      },
      "ConnectedAppList": {
        "additionalProperties": false,
        "properties": {
          "access_token_lifetime_seconds": {
            "description": "The tenant's access token lifetime. After a revoke, a resource server that validates tokens locally accepts the app's last access token for at most this long.",
            "type": "integer"
          },
          "connected_apps": {
            "items": {
              "$ref": "#/components/schemas/ConnectedApp"
            },
            "type": "array"
          }
        },
        "required": [
          "connected_apps",
          "access_token_lifetime_seconds"
        ],
        "type": "object"
      },
      "ConnectedAppResourceServer": {
        "additionalProperties": false,
        "properties": {
          "identifier": {
            "description": "The resource server's identifier, the `resource` the app asked for",
            "type": "string"
          },
          "name": {
            "description": "The resource server's name; absent when the tenant has no resource server with this identifier",
            "type": "string"
          }
        },
        "required": [
          "identifier"
        ],
        "type": "object"
      },
      "Error": {
        "additionalProperties": false,
        "description": "Standard error response",
        "properties": {
          "documentation_url": {
            "description": "Link to this error code on the Errors page.",
            "example": "https://docs.authalla.com/docs/errors#unauthorized",
            "format": "uri",
            "type": "string"
          },
          "error": {
            "description": "Machine-readable error code. See https://docs.authalla.com/docs/errors.",
            "enum": [
              "validation_error",
              "unauthorized",
              "forbidden",
              "not_found",
              "internal_error"
            ],
            "example": "unauthorized",
            "type": "string",
            "x-error-codes": {
              "forbidden": {
                "description": "The credential is valid but doesn't permit this request: it lacks the required scope, it isn't allowed to act on this tenant, the operation needs an account-global key, or the token isn't a client_credentials token.",
                "fix": "Read `message`. For a missing scope it names the scope; grant it to the app or key. Each operation's required scope is listed in the API reference.",
                "status": 403
              },
              "internal_error": {
                "description": "Something failed on Authalla's side.",
                "fix": "Retry with exponential backoff. If it persists, contact support with the time of the request.",
                "status": 500
              },
              "not_found": {
                "description": "The resource doesn't exist, or it isn't visible to this credential.",
                "fix": "Check the ID and that it belongs to the tenant or account the credential is bound to.",
                "status": 404
              },
              "unauthorized": {
                "description": "The credential is missing, malformed, expired, or revoked.",
                "fix": "Send `Authorization: Bearer \u003ccredential\u003e`. For the Application API, request a new client_credentials token. For the Management API, check that the account API key hasn't been revoked.",
                "status": 401
              },
              "validation_error": {
                "description": "The request doesn't match the OpenAPI spec: a required field is missing, a value has the wrong type, or it's out of range.",
                "fix": "Compare the request with the operation's schema in the API reference. `message` names the failing parameter or field.",
                "status": 400
              }
            }
          },
          "message": {
            "description": "Human-readable error message",
            "example": "The access token is invalid, expired or revoked",
            "type": "string"
          }
        },
        "required": [
          "error",
          "message",
          "documentation_url"
        ],
        "type": "object"
      },
      "Me": {
        "additionalProperties": false,
        "description": "The signed-in user",
        "properties": {
          "email": {
            "description": "Present when the token has the `email` scope",
            "format": "email",
            "type": "string"
          },
          "email_verified": {
            "description": "Present when the token has the `email` scope",
            "type": "boolean"
          },
          "id": {
            "description": "The user's ID, the `sub` of their tokens",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "updated_at"
        ],
        "type": "object"
      },
      "Session": {
        "additionalProperties": false,
        "description": "A browser the user is signed in on",
        "properties": {
          "auth_method": {
            "description": "How the user signed in, e.g. passkey, magic_link, pin_code, social-login-google",
            "type": "string"
          },
          "created_at": {
            "description": "When the user signed in",
            "format": "date-time",
            "type": "string"
          },
          "current": {
            "description": "The calling token was signed in from this session",
            "type": "boolean"
          },
          "expires_at": {
            "description": "When the session ends on its own",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "The session's ID (not its cookie); end the session with it",
            "type": "string"
          },
          "ip_address": {
            "description": "The browser's IP address when the user signed in",
            "type": "string"
          },
          "user_agent": {
            "description": "The browser's User-Agent when the user signed in",
            "type": "string"
          }
        },
        "required": [
          "id",
          "auth_method",
          "ip_address",
          "user_agent",
          "created_at",
          "expires_at",
          "current"
        ],
        "type": "object"
      },
      "SessionList": {
        "additionalProperties": false,
        "properties": {
          "sessions": {
            "items": {
              "$ref": "#/components/schemas/Session"
            },
            "type": "array"
          }
        },
        "required": [
          "sessions"
        ],
        "type": "object"
      },
      "UpdateMeRequest": {
        "additionalProperties": false,
        "properties": {
          "name": {
            "description": "The user's new name; surrounding whitespace is trimmed",
            "maxLength": 255,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "userAccessToken": {
        "description": "The signed-in user's access token, from the authorization code flow\non the tenant's host. Send it as `Authorization: Bearer \u003caccess token\u003e`.\n",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "/oauth2/authorize",
            "scopes": {
              "connected-apps:read": "List the apps connected to the user's account",
              "connected-apps:write": "Revoke an app's access to the user's account",
              "profile": "Read and change the user's name",
              "sessions:read": "List the browsers the user is signed in on",
              "sessions:write": "Sign the user out of one of their browsers"
            },
            "tokenUrl": "/oauth2/token"
          }
        },
        "type": "oauth2"
      }
    }
  },
  "info": {
    "contact": {
      "name": "Authalla Support",
      "url": "https://authalla.com"
    },
    "description": "The User API is what a signed-in user's own app calls about that user:\ntheir profile, the browsers they're signed in on and the apps connected\nto their account. Use it to build \"Where you're signed in\" and \"Apps with\naccess to your account\" into your product without a backend of your own.\n\nIt is Authalla's only REST API for a signed-in user.\n\n**Called with:** **the signed-in user's own access token**, not a secret\ncredential.\n\n**Reaches:** only that user, so your app may call it from a browser or a\nmobile app.\n\n**Use another API when** you act on other users or on configuration:\ncall it from your server with a secret credential. The **Application\nAPI** manages every user of a tenant with a client credentials token\n([spec](https://api.authalla.com/api/v1/openapi.json)), and the\n**Management API** configures tenants, apps and domains with an account\nAPI key ([spec](https://api.authalla.com/management/v1/openapi.json)).\nWhich API, and which credential, for which job:\nhttps://docs.authalla.com/docs/choosing-an-api\n\n## Host\n\nCall it on your tenant's own host, the one your app signs in through:\n`https://{tenant}.authalla.com` or your custom domain. The tenant comes\nfrom the host. `api.authalla.com` doesn't serve it. Browsers may call it\nfrom the origins in the tenant's CORS settings.\n\n## Access token\n\nSend the user's access token: `Authorization: Bearer \u003caccess token\u003e`.\nThe token must be:\n\n- an access token of this tenant, issued to your app for a user (an ID\n  token, or a `client_credentials` token, which has no user, is refused);\n- issued for your app itself, not for a Resource server: an agent holding\n  a token for your API or MCP server can't list or revoke the user's\n  other apps;\n- still active: it hasn't expired or been revoked, and the user is active.\n  Authalla checks this on every call, as introspection does.\n\nA token that fails any of these is `401` with a `WWW-Authenticate: Bearer\nerror=\"invalid_token\"` header.\n\n## Scopes\n\nEach operation needs one scope. Add the scopes to the app's allowed\nscopes and request them at sign-in; the user sees each one on the consent\nscreen. A token without the scope is `403` with\n`WWW-Authenticate: Bearer error=\"insufficient_scope\"`.\n\n- `profile`: read and change the user's name\n- `sessions:read` / `sessions:write`: list the user's browser sessions /\n  end one\n- `connected-apps:read` / `connected-apps:write`: list the apps connected\n  to the user's account / revoke one\n\nEnding a browser session never revokes an app, and revoking an app never\nends a browser session.\n",
    "title": "Authalla User API",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/me/v1": {
      "get": {
        "description": "Returns the signed-in user. `email` and `email_verified` are included\nwhen the token also has the `email` scope.\n\nRequires scope: `profile`.\n",
        "operationId": "getMe",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "email": "jane@example.com",
                  "email_verified": true,
                  "id": "usr_V1StGXR8Z5jdHi6BmyT",
                  "name": "Jane Doe",
                  "updated_at": "2026-09-26T10:04:11Z"
                },
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "description": "The signed-in user"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "profile"
            ]
          }
        ],
        "summary": "Get the user",
        "tags": [
          "Profile"
        ]
      },
      "patch": {
        "description": "Changes the signed-in user's name and returns the user.\n\nRequires scope: `profile`.\n",
        "operationId": "updateMe",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "name": "Jane Doe"
              },
              "schema": {
                "$ref": "#/components/schemas/UpdateMeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "email": "jane@example.com",
                  "email_verified": true,
                  "id": "usr_V1StGXR8Z5jdHi6BmyT",
                  "name": "Jane Doe",
                  "updated_at": "2026-09-26T10:04:11Z"
                },
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "description": "The updated user"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "profile"
            ]
          }
        ],
        "summary": "Update the user",
        "tags": [
          "Profile"
        ]
      }
    },
    "/me/v1/connected-apps": {
      "get": {
        "description": "Lists the apps that can still act for the user: those holding a\nremembered consent or a live refresh token. The app calling is marked\n`current`.\n\nRequires scope: `connected-apps:read`.\n",
        "operationId": "listConnectedApps",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "access_token_lifetime_seconds": 900,
                  "connected_apps": [
                    {
                      "client_id": "V1StGXR8_Z5jdHi6B-myT",
                      "current": true,
                      "id": "V1StGXR8_Z5jdHi6B-myT",
                      "last_used_at": "2026-09-26T10:04:11Z",
                      "name": "Acme web app",
                      "resource_servers": [],
                      "scopes": [
                        "openid",
                        "profile",
                        "email",
                        "sessions:read",
                        "connected-apps:read",
                        "connected-apps:write"
                      ],
                      "self_registered": false
                    },
                    {
                      "client_id": "https://claude.ai/oauth/mcp-oauth-client-metadata",
                      "current": false,
                      "id": "9kPq2LmN_x7RtY4ZaBcDe",
                      "last_used_at": "2026-09-26T09:58:30Z",
                      "name": "Claude",
                      "resource_servers": [
                        {
                          "identifier": "https://api.acme.example/mcp",
                          "name": "Acme MCP server"
                        }
                      ],
                      "scopes": [
                        "openid",
                        "offline_access",
                        "notes:read"
                      ],
                      "self_registered": true
                    }
                  ]
                },
                "schema": {
                  "$ref": "#/components/schemas/ConnectedAppList"
                }
              }
            },
            "description": "The user's connected apps"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "connected-apps:read"
            ]
          }
        ],
        "summary": "List the user's connected apps",
        "tags": [
          "Connected apps"
        ]
      }
    },
    "/me/v1/connected-apps/{id}": {
      "delete": {
        "description": "Takes the app's access to the user's account away: its refresh and\naccess tokens are revoked, its sign-ins not yet finished can't be, and\nthe user's consent is forgotten, so connecting it again asks again.\nA resource server that introspects tokens refuses the app's next\ncall; one that validates tokens locally accepts its access token\nuntil it expires, within `access_token_lifetime_seconds`. The user's\nbrowser sessions aren't touched. Revoking the `current` app is\nallowed and revokes the calling token too.\n\nRequires scope: `connected-apps:write`.\n",
        "operationId": "revokeConnectedApp",
        "parameters": [
          {
            "$ref": "#/components/parameters/ConnectedAppId"
          }
        ],
        "responses": {
          "204": {
            "description": "The app's access was revoked"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "connected-apps:write"
            ]
          }
        ],
        "summary": "Revoke a connected app",
        "tags": [
          "Connected apps"
        ]
      }
    },
    "/me/v1/sessions": {
      "get": {
        "description": "Lists the browsers the user is signed in on in this tenant, newest\nfirst. The session the calling token was signed in from is marked\n`current`; tokens issued before Authalla recorded sessions on tokens\nhave none.\n\nRequires scope: `sessions:read`.\n",
        "operationId": "listSessions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "sessions": [
                    {
                      "auth_method": "passkey",
                      "created_at": "2026-09-26T09:12:40Z",
                      "current": true,
                      "expires_at": "2026-10-26T09:12:40Z",
                      "id": "V1StGXR8_Z5jdHi6B-myT",
                      "ip_address": "203.0.113.7",
                      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"
                    },
                    {
                      "auth_method": "social-login-google",
                      "created_at": "2026-09-20T18:03:02Z",
                      "current": false,
                      "expires_at": "2026-10-20T18:03:02Z",
                      "id": "4f90d13a42b7c8e1f0a9b",
                      "ip_address": "198.51.100.23",
                      "user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"
                    }
                  ]
                },
                "schema": {
                  "$ref": "#/components/schemas/SessionList"
                }
              }
            },
            "description": "The user's browser sessions"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "sessions:read"
            ]
          }
        ],
        "summary": "List the user's sessions",
        "tags": [
          "Sessions"
        ]
      }
    },
    "/me/v1/sessions/{id}": {
      "delete": {
        "description": "Signs the user out of one browser: its next request to Authalla\nstarts signed out. The apps connected from that browser keep their\naccess; revoke them with the connected apps endpoints. Ending the\n`current` session is allowed; the calling token keeps working.\n\nRequires scope: `sessions:write`.\n",
        "operationId": "endSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionId"
          }
        ],
        "responses": {
          "204": {
            "description": "The session ended"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "userAccessToken": [
              "sessions:write"
            ]
          }
        ],
        "summary": "End a session",
        "tags": [
          "Sessions"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "Your tenant's host",
      "url": "https://{host}",
      "variables": {
        "host": {
          "default": "your-tenant.authalla.com",
          "description": "Your tenant's subdomain host, or a custom domain"
        }
      }
    }
  ],
  "tags": [
    {
      "description": "The signed-in user",
      "name": "Profile"
    },
    {
      "description": "The browsers the user is signed in on",
      "name": "Sessions"
    },
    {
      "description": "The apps that can act for the user",
      "name": "Connected apps"
    }
  ]
}