{
  "components": {
    "responses": {
      "BadRequestError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#bad_request",
              "error": "bad_request",
              "message": "Invalid request parameters"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Invalid request parameters"
      },
      "ConflictError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#conflict",
              "error": "conflict",
              "message": "Resource already exists"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Conflict (resource already exists)"
      },
      "ForbiddenError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#forbidden",
              "error": "forbidden",
              "message": "Insufficient scope"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Forbidden (insufficient scope or invalid token type)"
      },
      "InternalError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#internal_error",
              "error": "internal_error",
              "message": "An unexpected error occurred"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Internal server error"
      },
      "NotFoundError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#not_found",
              "error": "not_found",
              "message": "Resource not found"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Resource not found"
      },
      "UnauthorizedError": {
        "content": {
          "application/json": {
            "example": {
              "documentation_url": "https://docs.authalla.com/docs/errors#unauthorized",
              "error": "unauthorized",
              "message": "Missing authorization header"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Missing or invalid access token"
      }
    },
    "schemas": {
      "CreateUserRequest": {
        "description": "Request body for creating a new user",
        "properties": {
          "email": {
            "format": "email",
            "maxLength": 254,
            "type": "string"
          },
          "name": {
            "maxLength": 255,
            "minLength": 1,
            "type": "string"
          },
          "scopes": {
            "items": {
              "maxLength": 255,
              "type": "string"
            },
            "maxItems": 100,
            "type": "array"
          }
        },
        "required": [
          "email",
          "name"
        ],
        "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#bad_request",
            "format": "uri",
            "type": "string"
          },
          "error": {
            "description": "Machine-readable error code. See https://docs.authalla.com/docs/errors.",
            "enum": [
              "bad_request",
              "validation_error",
              "unauthorized",
              "forbidden",
              "not_found",
              "conflict",
              "internal_error"
            ],
            "example": "bad_request",
            "type": "string",
            "x-error-codes": {
              "bad_request": {
                "description": "The request couldn't be processed: the body isn't valid JSON, or a value breaks a rule the schema can't express.",
                "fix": "Read `message`, which names the problem, and correct the request.",
                "status": 400
              },
              "conflict": {
                "description": "The request clashes with existing state, for example a user with that email already exists or the tenant already has a custom domain.",
                "fix": "Fetch the existing resource and update it instead, or remove it first.",
                "status": 409
              },
              "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": "Invalid request",
            "type": "string"
          }
        },
        "required": [
          "error",
          "message",
          "documentation_url"
        ],
        "type": "object"
      },
      "SignOutEverywhereRequest": {
        "properties": {
          "suspend": {
            "default": false,
            "description": "Also suspend the user",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "UpdateUserRequest": {
        "description": "Request body for updating a user",
        "properties": {
          "email": {
            "format": "email",
            "maxLength": 254,
            "type": "string"
          },
          "name": {
            "maxLength": 255,
            "type": "string"
          },
          "scopes": {
            "items": {
              "maxLength": 255,
              "type": "string"
            },
            "maxItems": 100,
            "type": "array"
          },
          "status": {
            "$ref": "#/components/schemas/UserStatus"
          }
        },
        "type": "object"
      },
      "User": {
        "additionalProperties": false,
        "description": "A user in a tenant",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "email": {
            "description": "User email address",
            "example": "user@example.com",
            "format": "email",
            "type": "string"
          },
          "email_verified": {
            "description": "Whether the user's email has been verified",
            "example": false,
            "type": "boolean"
          },
          "id": {
            "description": "Unique public identifier for the user",
            "example": "user_abc123xyz",
            "type": "string"
          },
          "last_login": {
            "description": "Timestamp of the user's last login (if any)",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "description": "User display name",
            "example": "Jane Doe",
            "type": "string"
          },
          "scopes": {
            "description": "Custom scopes granted to this user (used to gate non-standard OAuth2 scopes)",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "status": {
            "$ref": "#/components/schemas/UserStatus"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "email",
          "name",
          "email_verified",
          "status",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "UserListResponse": {
        "additionalProperties": false,
        "description": "Response containing a list of users with pagination info",
        "properties": {
          "limit": {
            "description": "Maximum number of results per page",
            "example": 100,
            "type": "integer"
          },
          "offset": {
            "description": "Number of results skipped",
            "example": 0,
            "type": "integer"
          },
          "total": {
            "description": "Total number of users returned",
            "example": 123,
            "type": "integer"
          },
          "users": {
            "items": {
              "$ref": "#/components/schemas/User"
            },
            "type": "array"
          }
        },
        "required": [
          "users",
          "total",
          "limit",
          "offset"
        ],
        "type": "object"
      },
      "UserStatus": {
        "description": "Current status of the user",
        "enum": [
          "active",
          "inactive",
          "suspended"
        ],
        "type": "string"
      }
    },
    "securitySchemes": {
      "oauth2ClientCredentials": {
        "description": "OAuth2 access token from the client credentials grant at\n`https://api.authalla.com/oauth2/token`. The token endpoint resolves the\ntenant from `client_id`, so no tenant subdomain is needed. Tokens expire\nafter 900 seconds (15 minutes); request a new one the same way.\n\nInclude in the Authorization header: `Authorization: Bearer YOUR_ACCESS_TOKEN`\n",
        "flows": {
          "clientCredentials": {
            "scopes": {
              "users:read": "Read users",
              "users:write": "Create, update, and delete users"
            },
            "tokenUrl": "/oauth2/token"
          }
        },
        "type": "oauth2"
      }
    }
  },
  "info": {
    "contact": {
      "name": "Authalla Support",
      "url": "https://authalla.com"
    },
    "description": "The Authalla Application API manages the **users of one tenant**.\n\n**Called with:** a **secret credential**, a tenant app's client\ncredentials access token, got with its client secret. Call it from your\nserver.\n\n**Reaches:** every user of the tenant, so the token and the client secret\nstay on your server and never go in a browser or a mobile app.\n\n**Use another API when:**\n\n- a signed-in user views or changes their own details, sessions or\n  connected apps: use the **User API**, called with that user's own\n  access token\n  ([spec](https://api.authalla.com/me/v1/openapi.json)).\n- you configure tenants, apps, domains, sign-in methods or the theme: use\n  the **Management API**, also called from your server with a secret\n  credential, an account API key\n  ([spec](https://api.authalla.com/management/v1/openapi.json)).\n\nWhich API, and which credential, for which job:\nhttps://docs.authalla.com/docs/choosing-an-api\n\n## OAuth2 client credentials\n\nAll endpoints require an access token from the OAuth2 **client credentials** flow.\n\n### Getting an Access Token\n\n1. Create a Backend Service app in the Authalla dashboard\n2. Call the token endpoint with `grant_type=client_credentials` plus your `client_id` and `client_secret` to obtain an access token\n3. Include the access token in the `Authorization` header for all API requests\n\n### Scopes\n\nClient credentials access tokens are scoped to the data plane. Available scopes:\n\n- `users:read` / `users:write`\n\nManagement scopes (`tenants:*`, `apps:*`, `domains:*`, `sender-addresses:*`,\n`social-logins:*`, `theme:*`) are not grantable to a tenant client\ncredentials token; request them on the Management API's account API key\ninstead.\n\nIf no scopes are requested at the token endpoint, the token is granted all scopes allowed for the app.\n",
    "license": {
      "name": "Proprietary"
    },
    "title": "Authalla Application API",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/api/v1/users": {
      "get": {
        "description": "List users in the tenant associated with the access token.\n\nRequires scope: `users:read`.\n",
        "operationId": "listUsers",
        "parameters": [
          {
            "description": "Maximum number of users to return",
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 100,
              "maximum": 1000,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Number of users to skip",
            "in": "query",
            "name": "offset",
            "schema": {
              "default": 0,
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "description": "Search by email or name (case-insensitive substring match)",
            "in": "query",
            "name": "search",
            "schema": {
              "maxLength": 255,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "limit": 100,
                  "offset": 0,
                  "total": 1,
                  "users": [
                    {
                      "created_at": "2026-03-02T09:15:00Z",
                      "email": "jane@example.com",
                      "email_verified": true,
                      "id": "user_abc123xyz",
                      "last_login": "2026-03-04T14:40:12Z",
                      "name": "Jane Doe",
                      "scopes": [
                        "app:read"
                      ],
                      "status": "active",
                      "updated_at": "2026-03-04T14:40:12Z"
                    }
                  ]
                },
                "schema": {
                  "$ref": "#/components/schemas/UserListResponse"
                }
              }
            },
            "description": "List of users"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:read"
            ]
          }
        ],
        "summary": "List users",
        "tags": [
          "Users"
        ]
      },
      "post": {
        "description": "Create a new user in the tenant associated with the access token.\n\nRequires scope: `users:write`.\n",
        "operationId": "createUser",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "email": "jane@example.com",
                "name": "Jane Doe",
                "scopes": [
                  "app:read"
                ]
              },
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "created_at": "2026-03-02T09:15:00Z",
                  "email": "jane@example.com",
                  "email_verified": false,
                  "id": "user_abc123xyz",
                  "last_login": null,
                  "name": "Jane Doe",
                  "scopes": [
                    "app:read"
                  ],
                  "status": "active",
                  "updated_at": "2026-03-02T09:15:00Z"
                },
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            },
            "description": "User created successfully"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:write"
            ]
          }
        ],
        "summary": "Create user",
        "tags": [
          "Users"
        ]
      }
    },
    "/api/v1/users/{id}": {
      "delete": {
        "description": "Delete a user by public ID in the tenant associated with the access token.\n\nRequires scope: `users:write`.\n",
        "operationId": "deleteUser",
        "parameters": [
          {
            "description": "User public ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "user_abc123xyz",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "User deleted successfully"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:write"
            ]
          }
        ],
        "summary": "Delete user",
        "tags": [
          "Users"
        ]
      },
      "get": {
        "description": "Get a user by public ID in the tenant associated with the access token.\n\nRequires scope: `users:read`.\n",
        "operationId": "getUser",
        "parameters": [
          {
            "description": "User public ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "user_abc123xyz",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "created_at": "2026-03-02T09:15:00Z",
                  "email": "jane@example.com",
                  "email_verified": true,
                  "id": "user_abc123xyz",
                  "last_login": "2026-03-04T14:40:12Z",
                  "name": "Jane Doe",
                  "scopes": [
                    "app:read"
                  ],
                  "status": "active",
                  "updated_at": "2026-03-04T14:40:12Z"
                },
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            },
            "description": "User details"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:read"
            ]
          }
        ],
        "summary": "Get user",
        "tags": [
          "Users"
        ]
      },
      "put": {
        "description": "Update an existing user by public ID in the tenant associated with the access token.\n\nRequires scope: `users:write`.\n",
        "operationId": "updateUser",
        "parameters": [
          {
            "description": "User public ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "user_abc123xyz",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "name": "Jane Smith",
                "scopes": [
                  "app:read",
                  "app:write"
                ],
                "status": "active"
              },
              "schema": {
                "$ref": "#/components/schemas/UpdateUserRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "created_at": "2026-03-02T09:15:00Z",
                  "email": "jane@example.com",
                  "email_verified": true,
                  "id": "user_abc123xyz",
                  "last_login": "2026-03-04T14:40:12Z",
                  "name": "Jane Smith",
                  "scopes": [
                    "app:read",
                    "app:write"
                  ],
                  "status": "active",
                  "updated_at": "2026-03-04T14:40:12Z"
                },
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            },
            "description": "User updated successfully"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:write"
            ]
          }
        ],
        "summary": "Update user",
        "tags": [
          "Users"
        ]
      }
    },
    "/api/v1/users/{id}/sign-out-everywhere": {
      "post": {
        "description": "End every browser session of the user and revoke every app connected\nto their account: remembered consents are forgotten and all the\nuser's refresh and access tokens are revoked. Resource servers that\nintrospect tokens refuse them at once; those that validate them\nlocally accept them until they expire. With `suspend`, the user is\nalso suspended so they can't sign in again.\n\nRequires scope: `users:write`.\n",
        "operationId": "signOutUserEverywhere",
        "parameters": [
          {
            "description": "User public ID",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": "user_abc123xyz",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "suspend": true
              },
              "schema": {
                "$ref": "#/components/schemas/SignOutEverywhereRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "User signed out everywhere"
          },
          "400": {
            "$ref": "#/components/responses/BadRequestError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": [
          {
            "oauth2ClientCredentials": [
              "users:write"
            ]
          }
        ],
        "summary": "Sign a user out everywhere",
        "tags": [
          "Users"
        ]
      }
    }
  },
  "servers": [
    {
      "description": "Production API server",
      "url": "https://api.authalla.com"
    }
  ],
  "tags": [
    {
      "description": "User management operations (tenant-scoped)",
      "name": "Users"
    }
  ]
}