{
    "openapi": "3.2.0",
    "info": {
        "title": "Central de Identidade Vetor Zero / Lobo",
        "description": "Any endpoint may answer 429, with Retry-After in seconds, to a client past its rate limit. The limit is per client, whether the token is its own or a user's, and only a client sending far more than an integration needs ever reaches it.",
        "version": "1.0.0"
    },
    "paths": {
        "/api/v1/users/{id}/emails": {
            "post": {
                "tags": [
                    "Users"
                ],
                "summary": "Adds the address unverified and mails the user a verification link; adding it again resends\nthe link. The link itself never comes back here: only whoever reads that mailbox may verify.",
                "description": "With `verified`, the caller vouches for the address instead (say, an import trusting another\nsystem's data): it's added verified, or promoted if pending, and nothing is mailed.",
                "operationId": "post_app_api_v1_user_email_add",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/AddEmailRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The user, with the new address.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    },
                    "409": {
                        "description": "Unverified: the address is already verified on this user. Verified: on another user."
                    },
                    "422": {
                        "description": "The body failed validation."
                    }
                }
            }
        },
        "/api/v1/users/{id}/emails/{address}": {
            "delete": {
                "tags": [
                    "Users"
                ],
                "operationId": "delete_app_api_v1_user_email_remove",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "address",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "The address is gone."
                    },
                    "404": {
                        "description": "No user with this id, or no such address on it."
                    },
                    "409": {
                        "description": "The address is the primary one."
                    }
                }
            }
        },
        "/api/v1/users/email-owners": {
            "query": {
                "tags": [
                    "Users"
                ],
                "summary": "The owner of each address, for batches: the user holding it as primary or as a verified\nsecondary, the same rule as `ownsEmail` on the listing. QUERY (RFC 10008) is safe and\nidempotent like GET, but carries the list in the body.",
                "operationId": "query_app_api_v1_user_emailowner_query",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/EmailOwnersRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The owners.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/EmailOwners"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "No addresses, more than 100, or a malformed one."
                    }
                }
            }
        },
        "/api/v1/users/{id}/employment": {
            "put": {
                "tags": [
                    "Users"
                ],
                "operationId": "put_app_api_v1_user_employment_update",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/EmploymentRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    },
                    "422": {
                        "description": "The body failed validation, or names an unknown organization."
                    }
                }
            }
        },
        "/api/v1/users/{id}/deactivate": {
            "post": {
                "tags": [
                    "Users"
                ],
                "summary": "Signs the user out of every client and revokes their tokens. Already inactive is a no-op.",
                "operationId": "post_app_api_v1_user_lifecycle_deactivate",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The deactivated user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    },
                    "422": {
                        "description": "A user token tried to deactivate its own user."
                    }
                }
            }
        },
        "/api/v1/users/{id}/reactivate": {
            "post": {
                "tags": [
                    "Users"
                ],
                "summary": "Already active is a no-op.",
                "operationId": "post_app_api_v1_user_lifecycle_reactivate",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    },
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The reactivated user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    }
                }
            }
        },
        "/api/v1/users": {
            "get": {
                "tags": [
                    "Users"
                ],
                "summary": "Offset pagination holds up here: oldest first, and users are deactivated rather than\ndeleted, so a new user only ever lands on the last page.",
                "operationId": "get_app_api_v1_user_index",
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "maximum": 100,
                            "minimum": 1
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 0,
                            "minimum": 0
                        }
                    },
                    {
                        "name": "email",
                        "in": "query",
                        "description": "Only the primary address.",
                        "required": false,
                        "schema": {
                            "description": "Only the primary address.",
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null
                        }
                    },
                    {
                        "name": "ownsEmail",
                        "in": "query",
                        "description": "The primary address or a verified one: whoever owns the address. Unverified ones never match.",
                        "required": false,
                        "schema": {
                            "description": "The primary address or a verified one: whoever owns the address. Unverified ones never match.",
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null
                        }
                    },
                    {
                        "name": "active",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null,
                            "enum": [
                                "true",
                                "false"
                            ]
                        }
                    },
                    {
                        "name": "claimable",
                        "in": "query",
                        "description": "Not yet taken over by anyone signing in.",
                        "required": false,
                        "schema": {
                            "description": "Not yet taken over by anyone signing in.",
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null,
                            "enum": [
                                "true",
                                "false"
                            ]
                        }
                    },
                    {
                        "name": "employer",
                        "in": "query",
                        "description": "The employer's domain, or `none` for users without one.",
                        "required": false,
                        "schema": {
                            "description": "The employer's domain, or `none` for users without one.",
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null
                        }
                    },
                    {
                        "name": "served",
                        "in": "query",
                        "description": "The domain of an organization the user serves.",
                        "required": false,
                        "schema": {
                            "description": "The domain of an organization the user serves.",
                            "type": [
                                "string",
                                "null"
                            ],
                            "default": null
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "A page of users.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserPage"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "A page out of bounds, or a malformed filter."
                    }
                }
            },
            "post": {
                "tags": [
                    "Users"
                ],
                "summary": "The user is born claimable: whoever first signs in with a Google account verified for the\nemail takes it over. The invite email is a courtesy; if it fails, the user still exists. One\ncreated inactive, or with `invite` off, gets none.",
                "operationId": "post_app_api_v1_user_create",
                "parameters": [
                    {
                        "$ref": "#/components/parameters/IdempotencyKey"
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/CreateUserRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "The new user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "The email already belongs to a user, whose id comes as `user_id`, or the Slack account does. Nothing is changed."
                    },
                    "422": {
                        "description": "The body failed validation, or names an unknown organization."
                    }
                }
            }
        },
        "/api/v1/users/{id}": {
            "get": {
                "tags": [
                    "Users"
                ],
                "operationId": "get_app_api_v1_user_show",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "The user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    }
                }
            },
            "put": {
                "tags": [
                    "Users"
                ],
                "operationId": "put_app_api_v1_user_update",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/UpdateUserRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated user.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UserResource"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No user with this id."
                    },
                    "409": {
                        "description": "The email already belongs to another user, whose id comes as `user_id`, or the Slack account does."
                    },
                    "422": {
                        "description": "The body failed validation."
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "AddEmailRequest": {
                "required": [
                    "address"
                ],
                "properties": {
                    "address": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "verified": {
                        "description": "True takes the address as verified without the user proving it, and mails nothing.",
                        "type": "boolean",
                        "default": false
                    },
                    "reason": {
                        "description": "Why the address is taken as verified, for the audit log. Required with `verified`.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null
                    }
                },
                "type": "object"
            },
            "EmailOwnersRequest": {
                "required": [
                    "emails"
                ],
                "properties": {
                    "emails": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "maxItems": 100,
                        "minItems": 1
                    }
                },
                "type": "object"
            },
            "EmploymentRequest": {
                "required": [
                    "servedOrganizations"
                ],
                "properties": {
                    "employer": {
                        "description": "The employer's domain, null for none.",
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "servedOrganizations": {
                        "description": "Domains of the organizations served besides the employer.",
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "type": "object"
            },
            "PageQuery": {
                "properties": {
                    "limit": {
                        "type": "integer",
                        "default": 50,
                        "maximum": 100,
                        "minimum": 1
                    },
                    "offset": {
                        "type": "integer",
                        "default": 0,
                        "minimum": 0
                    }
                },
                "type": "object"
            },
            "UserFilter": {
                "properties": {
                    "email": {
                        "description": "Only the primary address.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null
                    },
                    "ownsEmail": {
                        "description": "The primary address or a verified one: whoever owns the address. Unverified ones never match.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null
                    },
                    "active": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null,
                        "enum": [
                            "true",
                            "false"
                        ]
                    },
                    "claimable": {
                        "description": "Not yet taken over by anyone signing in.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null,
                        "enum": [
                            "true",
                            "false"
                        ]
                    },
                    "employer": {
                        "description": "The employer's domain, or `none` for users without one.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null
                    },
                    "served": {
                        "description": "The domain of an organization the user serves.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "default": null
                    }
                },
                "type": "object"
            },
            "SlackRequest": {
                "required": [
                    "userId",
                    "teamId"
                ],
                "properties": {
                    "userId": {
                        "description": "Starts with U or W.",
                        "type": "string",
                        "example": "U012AB3CD",
                        "maxLength": 32
                    },
                    "teamId": {
                        "description": "Starts with T.",
                        "type": "string",
                        "example": "T0123456789",
                        "maxLength": 32
                    }
                },
                "type": "object"
            },
            "CreateUserRequest": {
                "required": [
                    "name",
                    "email"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "email": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "active": {
                        "description": "False creates the user already deactivated, with no invite: for importing people who left.",
                        "type": "boolean",
                        "default": true
                    },
                    "invite": {
                        "description": "False skips the invite email. An inactive user never gets one.",
                        "type": "boolean",
                        "default": true
                    },
                    "slack": {
                        "description": "Omitted: no Slack account.",
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/SlackRequest"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "default": null
                    },
                    "employment": {
                        "description": "Same shape as the body of `PUT /users/{id}/employment`. Omitted: no employment.",
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/EmploymentRequest"
                            },
                            {
                                "type": "null"
                            }
                        ],
                        "default": null
                    }
                },
                "type": "object"
            },
            "UpdateUserRequest": {
                "required": [
                    "name",
                    "email"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 255
                    },
                    "email": {
                        "description": "The primary address. Free while the user has no verified address (a pending invite); after that, one of their verified addresses.",
                        "type": "string",
                        "maxLength": 255
                    },
                    "slack": {
                        "description": "Null for none.",
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/SlackRequest"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    }
                },
                "type": "object"
            },
            "EmailResource": {
                "required": [
                    "address",
                    "verified"
                ],
                "properties": {
                    "address": {
                        "type": "string"
                    },
                    "verified": {
                        "type": "boolean"
                    }
                },
                "type": "object"
            },
            "OrganizationResource": {
                "required": [
                    "domain",
                    "name"
                ],
                "properties": {
                    "domain": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "SlackResource": {
                "required": [
                    "userId",
                    "teamId"
                ],
                "properties": {
                    "userId": {
                        "type": "string"
                    },
                    "teamId": {
                        "type": "string"
                    }
                },
                "type": "object"
            },
            "UserResource": {
                "required": [
                    "id",
                    "name",
                    "email",
                    "emails",
                    "servedOrganizations",
                    "active",
                    "claimable",
                    "createdAt"
                ],
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "description": "The primary address, one of `emails`.",
                        "type": "string"
                    },
                    "emails": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/EmailResource"
                        }
                    },
                    "employer": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/OrganizationResource"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "servedOrganizations": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/OrganizationResource"
                        }
                    },
                    "slack": {
                        "oneOf": [
                            {
                                "$ref": "#/components/schemas/SlackResource"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "picture": {
                        "description": "The primary Google account's avatar, the same as the OIDC `picture` claim. Hotlinked from Google.",
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri"
                    },
                    "active": {
                        "type": "boolean"
                    },
                    "claimable": {
                        "description": "Taken over by whoever next signs in with a Google account verified for `email`.",
                        "type": "boolean"
                    },
                    "createdAt": {
                        "type": "string",
                        "format": "date-time"
                    }
                },
                "type": "object"
            },
            "EmailOwners": {
                "required": [
                    "items"
                ],
                "properties": {
                    "items": {
                        "description": "Keyed by each address as it was sent; null when nobody owns it.",
                        "type": "object",
                        "additionalProperties": {
                            "oneOf": [
                                {
                                    "$ref": "#/components/schemas/UserResource"
                                },
                                {
                                    "type": "null"
                                }
                            ]
                        }
                    }
                },
                "type": "object"
            },
            "UserPage": {
                "required": [
                    "items",
                    "total"
                ],
                "properties": {
                    "items": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/UserResource"
                        }
                    },
                    "total": {
                        "description": "Users across every page.",
                        "type": "integer"
                    },
                    "next": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "prev": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "type": "object"
            }
        },
        "parameters": {
            "IdempotencyKey": {
                "name": "Idempotency-Key",
                "in": "header",
                "description": "Sending the same request again under the same key replays the first successful response (marked Idempotent-Replayed) instead of running it twice. Reusing a key for a different request is a 422; while the first is still running, a 409. Keys are kept for at least 7 days.",
                "required": false,
                "schema": {
                    "type": "string",
                    "maxLength": 255
                }
            }
        },
        "securitySchemes": {
            "bearer": {
                "type": "http",
                "description": "An access token already in hand, from POST /token (never the client secret itself): client_credentials to act as the client, authorization_code to act as the user.",
                "scheme": "bearer"
            },
            "client": {
                "type": "oauth2",
                "description": "Act as the client, with its own roles. Needs the client_credentials grant and API access.",
                "flows": {
                    "clientCredentials": {
                        "tokenUrl": "/token",
                        "scopes": {}
                    }
                }
            },
            "user": {
                "type": "oauth2",
                "description": "Act as yourself, logging in to the Central. Needs a public client with the authorization_code grant, API access and the redirect URI below.",
                "flows": {
                    "authorizationCode": {
                        "authorizationUrl": "/authorize",
                        "tokenUrl": "/token",
                        "scopes": {}
                    }
                }
            }
        }
    },
    "security": [
        {
            "bearer": []
        },
        {
            "client": []
        },
        {
            "user": []
        }
    ],
    "tags": [
        {
            "name": "Users"
        }
    ],
    "externalDocs": {
        "description": "Central de Identidade: login, integration checklist and the rest of the docs",
        "url": "/docs/"
    }
}