diff --git a/docs/deep-links.md b/docs/deep-links.md deleted file mode 100644 index 3464d01a..00000000 --- a/docs/deep-links.md +++ /dev/null @@ -1,119 +0,0 @@ -# Client deep links - -The desktop client (`owpengram-desktop-client`, a Telegram Desktop fork) resolves -`tg://` URIs and `https://t.me/` links through the same handler table: -`Telegram/SourceFiles/core/local_url_handlers.cpp`. Both schemes are normalized to the -same `?` form before dispatch, so every entry below works as either -`tg://join?invite=...` or `t.me/join/...` (the client also accepts the shorter -`t.me/+...` / `t.me/joinchat/...` aliases, which get rewritten to `join?invite=` -before reaching this table). - -Patterns are matched in order top to bottom; the first regex match wins. - -## Path handlers - -| Path | Handler | Purpose | -|---|---|---| -| `join?invite=` | `JoinGroupByHash` | Join a chat/channel via invite link hash. | -| `addlist?slug=` | `JoinFilterBySlug` | Import a shareable chat folder. | -| `addstickers?set=` / `addemoji?set=` | `ShowStickerSet` | Open a sticker or custom-emoji set for install. | -| `addtheme?slug=` | `ShowTheme` | Open a shared theme for install. | -| `addstyle?slug=` | `ShowAiStyle` | Open a shared AI compose style. | -| `setlanguage?lang=` | `SetLanguage` | Switch/install the app's interface language. | -| `msg_url?` | `ShareUrl` | Open the "share to chat" composer for an external URL. | -| `confirmphone?` | `ConfirmPhone` | Confirm a phone number transfer/login code sent by SMS. | -| `socks?` | `ApplySocksProxy` | Add/apply a SOCKS5 proxy config. | -| `proxy?` | `ApplyMtprotoProxy` | Add/apply an MTProto proxy config. | -| `webproxy?` | `ApplyWebProxy` | Add/apply a web proxy config. | -| `passport?` | `ShowPassport` | Telegram Passport authorization request. | -| `bg?` | `ShowWallPaper` | Open a shared chat wallpaper for install. | -| `resolve?` | `ResolveUsernameOrPhone` | Resolve `@username`/phone to a peer; see [resolve parameters](#resolve-parameters) below. This is the most common deep link kind. | -| `privatepost?` | `ResolvePrivatePost` | Open a specific message inside a private channel by internal channel id + message id. | -| `test_chat_theme?` | `ResolveTestChatTheme` | Preview a chat theme without installing it. | -| `invoice?` | `ResolveInvoice` | Open a payment invoice (Telegram Payments / Stars). | -| `premium_offer[?]` | `ResolvePremiumOffer` | Open the Premium subscription offer screen. | -| `premium_multigift?` | `ResolvePremiumMultigift` | Open the bulk/multi-recipient Premium gifting screen. | -| `login?code=` | `ResolveLoginCode` | Apply a login confirmation code (QR/code login flow). | -| `boost?` | `ResolveBoost` | Open the "boost this channel" flow. | -| `message?slug=` | `ResolveChatLink` | Open a pre-composed shareable chat/business link. | -| `stars_topup?` | `ResolveTopUp` | Open the Telegram Stars top-up screen. | -| `nft?slug=` | `ResolveUniqueGift` | Open a collectible/unique gift by slug. | -| `stargift_auction?slug=` | `ResolveGiftAuction` | Open a Star Gift auction listing. | -| `call?slug=` | `ResolveConferenceCall` | Join a group/conference call by invite slug. | -| `stars[?]` | `ResolveStarsSettings` | Open Telegram Stars balance/settings. | -| `ton[?]` / `grams[?]` | `ResolveTonSettings` | Open TON wallet settings. | -| `oauth?` | `ResolveOAuth` | Third-party OAuth authorization via Telegram. | -| *(anything else)* | `HandleUnknown` | Fallback — shown as an unrecognized-link message. | - -## Resolve parameters - -`resolve?domain=` (or the bare `t.me/` shorthand, which the -client rewrites to this form) accepts additional query parameters that change what -opens once the peer is resolved: - -| Parameter | Effect | -|---|---| -| `domain` | The `@username` to resolve (required unless using the bare `t.me/` form). | -| `phone` | Resolve by phone number instead of username. | -| `appname` | Open a Telegram Mini App by name instead of the peer's chat. | -| `startapp` | Start-parameter passed into a Mini App launch. | -| `attach` | Open the target's attach-menu bot alongside the chat. | -| `start` | Start-parameter for a bot (`/start `). | -| `startgroup` | Add a bot to a group with this start token (bot-in-group flow). | -| `startchannel` | Add a bot to a channel with this start token. | -| `post` | Jump to a specific message id in the resolved peer. | -| `story` | Open a specific story id from the resolved peer. | -| `comment` | Jump to a specific comment/reply message id (discussion groups). | -| `topic` | Open a specific forum topic id. | -| `thread` | Open a specific reply thread/topic id (older alias of `topic`). | -| `game` | Launch a specific inline game short name from a bot. | -| `text` | Pre-fill the message composer with this text. | -| `voicechat` / `videochat` / `livestream` | Join the peer's active voice chat / video chat / live stream by hash. | - -## Internal-only links - -`InternalUrlHandlers()` in the same file (`internal:`) covers links the app -generates for its own in-app UI (e.g. `internal:edit_peer/`, -`internal:common_groups/`, `internal:collectible_username/@`). These -are not meant to be shared externally and are not part of the public deep-link -surface — they only work as links the client itself produces inside its own UI. - -## OwpenGram (`owpg://`) links - -This fork adds a second scheme, handled in `Telegram/SourceFiles/core/application.cpp` -(`Application::openLocalUrl` / `Application::openOwpengramUrl`), for operating across -multiple OwpenGram-compatible backends rather than only the official Telegram DCs. - -| Form | Purpose | -|---|---| -| `owpg://addserver?host=&port=` | Opens `AddServerBox` pre-filled with just the address. **Deliberately carries nothing else** — no name, description, RSA key, or DC id. | -| `owpg://oauth?` / `owpg://resolve?` | Rewritten to the equivalent `tg://oauth` / `tg://resolve` command and dispatched on the current session — lets those two flows work on an OwpenGram account, not just official Telegram. | -| `owpg:///` | Routes a normal `t.me`-style path (`` = `+`, ``, etc.) to whichever logged-in account is on that server's ``, bypassing the official-Telegram-only guard. If no account on the device is logged into ``, the client shows an inform box naming the server instead of silently failing. | - -### Why `addserver` only takes host+port - -The RSA public key, main DC id, display name, and description are **never** accepted -from the link itself — see the comment above `kOwpgAddServerRe` in `application.cpp`. -Letting a link author supply the key/identity directly would be a MITM vector: whoever -crafts the link would control what the victim ends up trusting as that server's -identity, regardless of who actually operates the host. Only host+port travel in the -link; everything else is fetched live and is exactly as trustworthy as the TCP -connection to that host:port already is. - -Once `AddServerBox` has host+port, it calls `Owpengram::FetchServerInfo(host, port, …)`, -which does a plain HTTP `GET` against the server's own well-known endpoints on the -same port: - -| Endpoint | Returns | -|---|---| -| `GET :/owpengram/server-info` | RSA public key (PEM), main DC id, and optional operator-set name/description/icon-presence flag. | -| `GET :/owpengram/server-icon` | Raw icon bytes, only fetched when `server-info` reported `hasIcon: true`. | - -`RefreshServersInfo()` re-polls `server-info`/`server-icon` for every already-added -custom server to keep a cosmetic name/icon change in sync — it never touches host, -port, RSA key, DC id, or the server's local id, for the same MITM reason as above (see -the doc comment on `RefreshServersInfo` in `owpengram_servers.h`). - -This also means the RSA-key-in-a-URL scheme discussed earlier in this doc's history -is intentionally **not** how this client adds a server — that was a plausible-looking -design that this project already considered and rejected in favor of fetch-after-add. diff --git a/docs/openapi.yaml b/docs/openapi.yaml deleted file mode 100644 index 949123b5..00000000 --- a/docs/openapi.yaml +++ /dev/null @@ -1,3628 +0,0 @@ -openapi: 3.1.0 -info: - title: owpengram-server Admin APIs - version: "1.1.0" - description: | - Two independently authenticated HTTP admin surfaces, run as **separate - listeners in the same process** (`cmd/telesrv-admin`): - - 1. **Admin API** (`internal/adminapi`) — a bearer-token JSON API under - `/v1/*`, listening on `TELESRV_ADMIN_API_ADDR` - (default `127.0.0.1:2599`). Closed unless configured - (`TELESRV_ADMIN_API_ADDR` empty ⇒ off). This is the raw write surface. - 2. **Panel API** (`cmd/telesrv-admin`) — the cookie/CSRF-session JSON API - under `/api/*` behind the built admin SPA, listening on - `TELESRV_ADMIN_UI_ADDR` (default `127.0.0.1:2600`). Most reads go - straight to Postgres; every `/api/actions/*` mutation (plus - moderation and verification/bot-verification decisions) is relayed - through to the Admin API on your behalf, bearer-authenticated with - `TELESRV_ADMIN_API_TOKEN` — the panel itself only writes local state - directly for admin-operator accounts and server settings/identity. - - Both APIs' `POST` routes return a **command result** - (`CommandResult` — shared by both surfaces, same Go type). Panel API - action routes wrap a relayed Admin API call, so most of them can also - surface a `502` if that upstream call itself fails — except the - verification/bot-verification decision routes, which preserve the - upstream's real status (404/409/429/400) instead of flattening to 502. - - Panel API permissions are granular (`accounts.manage`, - `channels.read`, `content.manage`, …) — see each operation's - description for the exact permission it requires; `*` (only ever held - by the master/break-glass "owpengram" account or a token explicitly - granted it) satisfies every check. - - Built entirely from reading source directly (no prose doc was used or - trusted for either surface — an earlier version of this spec was - generated from `docs/admin-panel-api.en.md` and found to document - several routes, like `grant-stars`, that don't exist, while missing - ~40 real ones and understating the permission model; that doc should - be considered unreliable going forward): - `internal/adminapi/{server,rbac,verification,botverification}.go` and - `cmd/telesrv-admin/{server,security,session,adminauth,adminusers_api, - verification,botverification,serversettings}.go`. - contact: - name: owpengram-server - url: https://git.zio.sh/astra/owpengram - -servers: - - url: http://127.0.0.1:2599 - description: Admin API (internal/adminapi) — bearer token, TELESRV_ADMIN_API_ADDR - - url: http://127.0.0.1:2600 - description: Panel API (cmd/telesrv-admin) — cookie session + CSRF, TELESRV_ADMIN_UI_ADDR - -security: [] - -tags: - - { name: "Panel: Auth", description: "Panel API — login/logout/session/public branding" } - - { name: "Panel: Admin Operators", description: "Panel API — named admin_console_users accounts (requires admins.manage)" } - - { name: "Panel: Dashboard", description: "Panel API — dashboard summary (requires dashboard.read)" } - - { name: "Panel: Accounts", description: "Panel API — account browsing + actions (requires accounts.read / accounts.manage)" } - - { name: "Panel: Broadcasts", description: "Panel API — broadcast browsing + actions (requires broadcasts.read / broadcasts.send)" } - - { name: "Panel: Channels", description: "Panel API — channel/supergroup browsing + actions (requires channels.read / channels.manage)" } - - { name: "Panel: Bots", description: "Panel API — bot browsing + actions (requires bots.read / bots.manage / bots.token.read)" } - - { name: "Panel: Messages", description: "Panel API — message audit + deletion actions (requires messages.read / messages.manage)" } - - { name: "Panel: Content", description: "Panel API — custom emoji, sticker sets, GIF catalog (requires content.read / content.manage)" } - - { name: "Panel: Storage", description: "Panel API — storage stats + manual purge (requires storage.read / storage.manage)" } - - { name: "Panel: Moderation", description: "Panel API — moderation case queue, proxied to the Admin API (requires moderation.review)" } - - { name: "Panel: Usernames", description: "Panel API — reserved + collectible username lifecycle (requires usernames.read / usernames.manage)" } - - { name: "Panel: Official Verification", description: "Panel API — official verification queue (requires verification.review, revoke also needs verification.revoke)" } - - { name: "Panel: Bot Verification", description: "Panel API — third-party bot verification queue, hidden by default (requires botverification.review / botverification.manage)" } - - { name: "Panel: Server Settings", description: "Panel API — server identity, env, updates, restart (requires server.manage)" } - - { name: "AdminAPI: Accounts", description: "Admin API — account write actions" } - - { name: "AdminAPI: Channels", description: "Admin API — channel write actions" } - - { name: "AdminAPI: Bots & Broadcasts", description: "Admin API — bot/broadcast write actions" } - - { name: "AdminAPI: Messages", description: "Admin API — message deletion actions" } - - { name: "AdminAPI: Stickers", description: "Admin API — sticker set write actions" } - - { name: "AdminAPI: GIF Catalog", description: "Admin API — GIF catalog write actions" } - - { name: "AdminAPI: Storage", description: "Admin API — storage purge" } - - { name: "AdminAPI: Moderation", description: "Admin API — moderation case queue (raw)" } - - { name: "AdminAPI: Collectible Usernames", description: "Admin API — collectible username lifecycle" } - - { name: "AdminAPI: Reserved Usernames", description: "Admin API — operator username blocklist" } - - { name: "AdminAPI: Official Verification", description: "Admin API — official verification queue (raw)" } - - { name: "AdminAPI: Bot Verification", description: "Admin API — third-party bot verification queue (raw)" } - - { name: "AdminAPI: Health", description: "Admin API — unauthenticated health check" } - -paths: - # ============================================================ - # PANEL API (cmd/telesrv-admin) — cookie session + CSRF - # ============================================================ - - # --- Auth / session --- - - /api/login: - post: - tags: ["Panel: Auth"] - summary: Log in - security: [] - description: >- - Issues the signed telesrv_admin_session cookie and the - telesrv_admin_csrf cookie. The only mutating panel route without - CSRF (no session exists yet) — only the Origin header is checked. - `username` is always required: either the literal break-glass - username `"owpengram"` (checked against TELESRV_ADMIN_UI_PASSWORD/ - _TOKEN — reserved, no DB account may take it), or a named account - in `admin_console_users`. Every failure mode (unknown user, wrong - password, disabled account) returns the same 401 with the same - generic message, and an unknown-user attempt still burns a dummy - bcrypt compare, so failure modes cost the same time. - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [username, secret] - properties: - username: { type: string, description: "Break-glass username (\"owpengram\"), or a named admin_console_users account." } - secret: { type: string } - example: { username: "owpengram", secret: "letmein" } - responses: - "200": { description: OK, headers: { Set-Cookie: { schema: { type: string } } } } - "401": { $ref: "#/components/responses/PanelUnauthorized" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/logout: - post: - tags: ["Panel: Auth"] - summary: Log out - description: Requires an authenticated session; no specific permission. - security: [{ panelSession: [], panelCsrf: [] }] - responses: - "200": { description: OK } - - /api/session: - get: - tags: ["Panel: Auth"] - summary: Current session info - description: Requires an authenticated session; no specific permission. - security: [{ panelSession: [] }] - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - actor: { type: string } - permissions: { type: array, items: { type: string } } - csrf_token: { type: string } - "401": { $ref: "#/components/responses/PanelUnauthorized" } - - /api/public/branding: - get: - tags: ["Panel: Auth"] - summary: Public server branding (name/description), unauthenticated - description: Public route — shown on the login screen before signing in. - security: [] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - - /api/public/icon: - get: - tags: ["Panel: Auth"] - summary: Public server icon, unauthenticated - security: [] - responses: - "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } } - "404": { description: No icon set } - - # --- Admin operator accounts (admins.manage) --- - - /api/admin-users: - get: - tags: ["Panel: Admin Operators"] - summary: List admin operator accounts - description: Requires `admins.manage`. - security: [{ panelSession: [] }] - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - system: { type: object, description: "the break-glass account", properties: { username: { type: string, const: owpengram }, permissions: { type: array, items: { type: string } }, enabled: { type: boolean, const: true }, system: { type: boolean, const: true } } } - rows: { type: array, items: { type: object, additionalProperties: true } } - available_permissions: { type: array, items: { type: string } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/create-admin-operator: - post: - tags: ["Panel: Admin Operators"] - summary: Create a named admin operator account - description: Requires `admins.manage`. `reason` is mandatory (stricter than the generic action envelope). - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: { $ref: "#/components/requestBodies/PanelAdminUserAction" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/set-admin-operator-access: - post: - tags: ["Panel: Admin Operators"] - summary: Edit a named admin operator's permissions/enabled state - description: Requires `admins.manage`. `reason` is mandatory. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: { $ref: "#/components/requestBodies/PanelAdminUserAction" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/set-admin-operator-password: - post: - tags: ["Panel: Admin Operators"] - summary: Set a named admin operator's password - description: Requires `admins.manage`. `reason` is mandatory. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: { $ref: "#/components/requestBodies/PanelAdminUserAction" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - # --- Dashboard --- - - /api/dashboard: - get: - tags: ["Panel: Dashboard"] - summary: Dashboard summary - description: Requires `dashboard.read`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - # --- Accounts --- - - /api/accounts: - get: - tags: ["Panel: Accounts"] - summary: List/search accounts - description: Requires `accounts.read`. - security: [{ panelSession: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_active_us, in: query, description: microseconds, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - query: { type: string } - limit: { type: integer } - rows: { type: array, items: { $ref: "#/components/schemas/AccountRow" } } - has_more: { type: boolean } - next_before_id: { type: integer, format: int64 } - next_before_active_us: { type: integer, format: int64 } - listing: { type: boolean } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/accounts/stats: - get: - tags: ["Panel: Accounts"] - summary: Account statistics - description: Requires `accounts.read`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/accounts/shared-devices: - get: - tags: ["Panel: Accounts"] - summary: Accounts grouped by shared device fingerprint - description: Requires `accounts.read`. - security: [{ panelSession: [] }] - parameters: - - { name: offset, in: query, schema: { type: integer } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - limit: { type: integer } - offset: { type: integer } - rows: { type: array, items: { type: object, additionalProperties: true } } - has_more: { type: boolean } - next_offset: { type: integer } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/accounts/{id}: - get: - tags: ["Panel: Accounts"] - summary: Account card (profile, flags, statistics) - description: Requires `accounts.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - "404": { $ref: "#/components/responses/PanelNotFound" } - - /api/accounts/{id}/avatar: - get: - tags: ["Panel: Accounts"] - summary: Account avatar (binary, proxied from the Admin API) - description: Requires `accounts.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - "404": { description: Not found } - - /api/actions/set-frozen: - post: - tags: ["Panel: Accounts"] - summary: Freeze/unfreeze an account - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: { $ref: "#/components/requestBodies/PanelSetFrozen" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/grant-premium: - post: - tags: ["Panel: Accounts"] - summary: Grant Premium months - description: Requires `premium.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id, months], properties: { user_id: { type: integer, format: int64 }, months: { type: integer } } }] - example: { command_id: "grant-premium-001", reason: "Incident compensation", confirm: true, user_id: 123456789, months: 12 } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-verified: - post: - tags: ["Panel: Accounts"] - summary: Set/clear the legacy verified badge on a user - description: >- - Requires `verification.review` (not `accounts.manage`) — this sets - the boolean verified flag directly, separate from the official - verification-applications workflow, but shares its permission. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id, verified], properties: { user_id: { type: integer, format: int64 }, verified: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-flags: - post: - tags: ["Panel: Accounts"] - summary: Set scam/fake flags on a user - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, scam: { type: boolean }, fake: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-support: - post: - tags: ["Panel: Accounts"] - summary: Set/clear the support flag on a user - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id, support], properties: { user_id: { type: integer, format: int64 }, support: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-username: - post: - tags: ["Panel: Accounts"] - summary: Set a user's username - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id, username], properties: { user_id: { type: integer, format: int64 }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-profile: - post: - tags: ["Panel: Accounts"] - summary: Set a user's first/last name - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, first_name: { type: string }, last_name: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-phone: - post: - tags: ["Panel: Accounts"] - summary: Set a user's phone number - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id, phone], properties: { user_id: { type: integer, format: int64 }, phone: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-login-email: - post: - tags: ["Panel: Accounts"] - summary: Set/clear a user's login email - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, email: { type: string, description: "empty clears it" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-avatar: - post: - tags: ["Panel: Accounts"] - summary: Set a user's avatar (static image) - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: { $ref: "#/components/requestBodies/PanelUserAvatarUpload" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-avatar-video: - post: - tags: ["Panel: Accounts"] - summary: Set a user's avatar (animated video) - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields + user_id (int64), video_start_ts (float64)", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-color: - post: - tags: ["Panel: Accounts"] - summary: Set a user's profile/name color - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/PeerColorInput" }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-account-emoji-status: - post: - tags: ["Panel: Accounts"] - summary: Set a user's emoji status - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/EmojiStatusInput" }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/revoke-sessions: - post: - tags: ["Panel: Accounts"] - summary: Revoke a user's authorized sessions - description: Requires `accounts.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, hash: { type: integer, format: int64 }, keep_hash: { type: integer, format: int64 }, revoke_all: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Broadcasts --- - - /api/broadcasts: - get: - tags: ["Panel: Broadcasts"] - summary: List broadcasts - description: Requires `broadcasts.read`. - security: [{ panelSession: [] }] - parameters: - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: { limit: { type: integer }, rows: { type: array, items: { type: object, additionalProperties: true } }, has_more: { type: boolean }, next_before_id: { type: integer, format: int64 } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/create-broadcast: - post: - tags: ["Panel: Broadcasts"] - summary: Create a broadcast - description: Requires `broadcasts.send`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [message, target_mode], properties: { message: { type: string }, target_mode: { type: string }, user_ids: { type: array, items: { type: integer, format: int64 }, description: "only for target_mode=selected" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Channels --- - - /api/channels: - get: - tags: ["Panel: Channels"] - summary: List/search channels - description: Requires `channels.read`. - security: [{ panelSession: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_updated_us, in: query, description: microseconds, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/channels/{id}: - get: - tags: ["Panel: Channels"] - summary: Channel card - description: Requires `channels.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/channels/{id}/avatar: - get: - tags: ["Panel: Channels"] - summary: Channel avatar (binary, proxied from the Admin API) - description: Requires `channels.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/set-channel-flags: - post: - tags: ["Panel: Channels"] - summary: Set scam/fake flags on a channel - description: Requires `channels.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 }, scam: { type: boolean }, fake: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-avatar: - post: - tags: ["Panel: Channels"] - summary: Set a channel's avatar - description: Requires `channels.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields + channel_id (int64)", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-settings: - post: - tags: ["Panel: Channels"] - summary: Partially update channel settings - description: >- - Requires `channels.manage`. Every field besides `channel_id` is an - optional pointer — a partial PATCH: only fields present in the JSON - body are changed. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [channel_id] - properties: - channel_id: { type: integer, format: int64 } - gigagroup: { type: [boolean, "null"] } - antispam: { type: [boolean, "null"] } - participants_hidden: { type: [boolean, "null"] } - noforwards: { type: [boolean, "null"] } - join_to_send: { type: [boolean, "null"] } - join_request: { type: [boolean, "null"] } - slowmode_seconds: { type: [integer, "null"] } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-username: - post: - tags: ["Panel: Channels"] - summary: Set a channel's username - description: Requires `channels.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [channel_id, username], properties: { channel_id: { type: integer, format: int64 }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-color: - post: - tags: ["Panel: Channels"] - summary: Set a channel's profile/name color - description: Requires `channels.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/PeerColorInput" }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-emoji-status: - post: - tags: ["Panel: Channels"] - summary: Set a channel's emoji status - description: Requires `channels.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/EmojiStatusInput" }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-channel-verified: - post: - tags: ["Panel: Channels"] - summary: Set/clear the verified badge on a channel - description: Requires `verification.review` (not `channels.manage`). - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [channel_id, verified], properties: { channel_id: { type: integer, format: int64 }, verified: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Bots --- - - /api/bots: - get: - tags: ["Panel: Bots"] - summary: List/search bots - description: Requires `bots.read`. - security: [{ panelSession: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/bots/{id}: - get: - tags: ["Panel: Bots"] - summary: Bot card - description: Requires `bots.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/create-bot: - post: - tags: ["Panel: Bots"] - summary: Create a bot account - description: Requires `bots.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [owner_user_id, name, username], properties: { owner_user_id: { type: integer, format: int64 }, name: { type: string }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-bot: - post: - tags: ["Panel: Bots"] - summary: Delete a bot account - description: Requires `bots.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [bot_user_id], properties: { bot_user_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/export-bot-token: - post: - tags: ["Panel: Bots"] - summary: Export a bot's auth token - description: Requires `bots.token.read`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [bot_user_id], properties: { bot_user_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Messages --- - - /api/messages: - get: - tags: ["Panel: Messages"] - summary: Private message audit - description: Requires `messages.read`. Only queries when both `owner_user_id` and `peer_id` are > 0. - security: [{ panelSession: [] }] - parameters: - - { name: owner_user_id, in: query, schema: { type: integer, format: int64 } } - - { name: peer_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_date, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: { owner_user_id: { type: integer, format: int64 }, peer_id: { type: integer, format: int64 }, before_date: { type: integer, format: int64 }, before_id: { type: integer }, limit: { type: integer }, rows: { type: array, items: { type: object, additionalProperties: true } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/messages/detail: - get: - tags: ["Panel: Messages"] - summary: Private message detail - description: Requires `messages.read`. Both query params required. - security: [{ panelSession: [] }] - parameters: - - { name: owner_user_id, in: query, required: true, schema: { type: integer, format: int64 } } - - { name: msg_id, in: query, required: true, schema: { type: integer } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/messages/groups: - get: - tags: ["Panel: Messages"] - summary: Channel/group message audit - description: Requires `messages.read`. - security: [{ panelSession: [] }] - parameters: - - { name: channel_id, in: query, required: true, schema: { type: integer, format: int64 } } - - { name: before_date, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: { channel_id: { type: integer, format: int64 }, before_date: { type: integer, format: int64 }, before_id: { type: integer }, limit: { type: integer }, rows: { type: array, items: { type: object, additionalProperties: true } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/messages/groups/detail: - get: - tags: ["Panel: Messages"] - summary: Channel/group message detail - description: Requires `messages.read`. - security: [{ panelSession: [] }] - parameters: - - { name: channel_id, in: query, required: true, schema: { type: integer, format: int64 } } - - { name: msg_id, in: query, required: true, schema: { type: integer } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/delete-messages: - post: - tags: ["Panel: Messages"] - summary: Delete specific private messages - description: Requires `messages.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [owner_user_id, peer_id, ids], properties: { owner_user_id: { type: integer, format: int64 }, peer_id: { type: integer, format: int64 }, ids: { type: array, items: { type: integer } }, revoke: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-history: - post: - tags: ["Panel: Messages"] - summary: Delete a private chat's history - description: Requires `messages.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [owner_user_id, peer_id] - properties: - owner_user_id: { type: integer, format: int64 } - peer_id: { type: integer, format: int64 } - max_id: { type: integer } - min_date: { type: integer } - max_date: { type: integer } - max_batches: { type: integer } - just_clear: { type: boolean } - revoke: { type: boolean } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Content: emoji --- - - /api/emoji: - get: - tags: ["Panel: Content"] - summary: List/search custom emoji - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/emoji/{id}/animation: - get: - tags: ["Panel: Content"] - summary: Emoji Lottie animation - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Animation JSON bytes } - "403": { $ref: "#/components/responses/PanelForbidden" } - - # --- Content: stickers --- - - /api/stickers: - get: - tags: ["Panel: Content"] - summary: List sticker sets - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: - - { name: kind, in: query, schema: { type: string } } - responses: - "200": - description: OK - content: - application/json: - schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } }, max_items: { type: integer } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/stickers/{id}/documents: - get: - tags: ["Panel: Content"] - summary: Document ids in a sticker set - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { document_ids: { type: array, items: { type: integer, format: int64 } } } } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/stickers/documents/{id}/animation: - get: - tags: ["Panel: Content"] - summary: Sticker animation - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Animation bytes } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/set-sticker-set-archived: - post: - tags: ["Panel: Content"] - summary: Archive/unarchive a sticker set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [set_id, archived], properties: { set_id: { type: string, description: "int64, json:\",string\"" }, archived: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-sticker-set-sort-order: - post: - tags: ["Panel: Content"] - summary: Reorder a sticker set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [set_id, sort_order], properties: { set_id: { type: string, description: "int64, json:\",string\"" }, sort_order: { type: integer } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/rename-sticker-set: - post: - tags: ["Panel: Content"] - summary: Rename a sticker set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [set_id, title], properties: { set_id: { type: string, description: "int64, json:\",string\"" }, title: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-sticker-set: - post: - tags: ["Panel: Content"] - summary: Delete a sticker set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [set_id], properties: { set_id: { type: string, description: "int64, json:\",string\"" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/create-sticker-set: - post: - tags: ["Panel: Content"] - summary: Create a sticker set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields + title, short_name, kind, emoji, keywords (opt.)", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/add-sticker-to-set: - post: - tags: ["Panel: Content"] - summary: Add a sticker to a set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields + set_id (int64, json:\",string\"), emoji, keywords (opt.)", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/remove-sticker-from-set: - post: - tags: ["Panel: Content"] - summary: Remove a sticker from a set - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [set_id, document_id], properties: { set_id: { type: string, description: "int64, json:\",string\"" }, document_id: { type: string, description: "int64, json:\",string\"" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Content: GIF catalog --- - - /api/gif-catalog: - get: - tags: ["Panel: Content"] - summary: GIF catalog - description: Requires `content.read`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/gif-catalog/documents/{id}/preview: - get: - tags: ["Panel: Content"] - summary: GIF preview - description: Requires `content.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Preview bytes } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/create-gif-catalog-entry: - post: - tags: ["Panel: Content"] - summary: Add a GIF catalog entry - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields + title", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-gif-catalog-enabled: - post: - tags: ["Panel: Content"] - summary: Enable/disable a GIF catalog entry - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [id, enabled], properties: { id: { type: string, description: "int64, json:\",string\"" }, enabled: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-gif-catalog-sort-order: - post: - tags: ["Panel: Content"] - summary: Reorder a GIF catalog entry - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [id, sort_order], properties: { id: { type: string, description: "int64, json:\",string\"" }, sort_order: { type: integer } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-gif-catalog-category: - post: - tags: ["Panel: Content"] - summary: Set a GIF catalog entry's category - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [id, category], properties: { id: { type: string, description: "int64, json:\",string\"" }, category: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/auto-categorize-gif-catalog: - post: - tags: ["Panel: Content"] - summary: Auto-categorize uncategorized GIFs - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-uncategorized-gifs: - post: - tags: ["Panel: Content"] - summary: Delete every uncategorized GIF catalog entry - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-gif-catalog-entry: - post: - tags: ["Panel: Content"] - summary: Delete a GIF catalog entry - description: Requires `content.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [id], properties: { id: { type: string, description: "int64, json:\",string\"" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Storage --- - - /api/storage/stats: - get: - tags: ["Panel: Storage"] - summary: Object storage statistics - description: Requires `storage.read`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/storage/accounts: - get: - tags: ["Panel: Storage"] - summary: Storage usage broken down by account - description: Requires `storage.read`. - security: [{ panelSession: [] }] - parameters: - - { name: offset, in: query, schema: { type: integer } } - - { $ref: "#/components/parameters/Limit" } - - { name: order, in: query, schema: { type: string, enum: [asc, desc] } } - - { name: q, in: query, schema: { type: string } } - - { name: sort, in: query, schema: { type: string } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/storage-manual-purge: - post: - tags: ["Panel: Storage"] - summary: Manually purge storage by category - description: Requires `storage.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [categories] - properties: - categories: { type: array, items: { type: string, enum: [photo, video, round_video, gif, music, voice, file] } } - include_avatars: { type: boolean } - created_before: { type: [string, "null"], format: date-time, description: "RFC3339; null = no age filter" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Moderation (proxied to the Admin API) --- - - /api/moderation/cases: - get: - tags: ["Panel: Moderation"] - summary: List moderation cases - description: Requires `moderation.review`. Entire query string forwarded to the Admin API's `GET /v1/moderation/cases` unchanged. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/moderation/cases/{id}: - get: - tags: ["Panel: Moderation"] - summary: Moderation case detail - description: Requires `moderation.review`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/moderation/reports/{id}: - get: - tags: ["Panel: Moderation"] - summary: Moderation report detail - description: Requires `moderation.review`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/moderation/cases/{id}/claim: - post: - tags: ["Panel: Moderation"] - summary: Claim a moderation case - description: Requires `moderation.review`. Relayed to the Admin API — `actor` is injected from the session, `command_id` minted if absent. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/moderation/cases/{id}/decide: - post: - tags: ["Panel: Moderation"] - summary: Decide a moderation case - description: Requires `moderation.review`. Relayed to the Admin API's `POST /v1/moderation/cases/{id}/decide`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/moderation/cases/{id}/appeals/{appeal_id}/review: - post: - tags: ["Panel: Moderation"] - summary: Review an appeal on a moderation case - description: Requires `moderation.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: - - { $ref: "#/components/parameters/PathId" } - - { name: appeal_id, in: path, required: true, schema: { type: integer, format: int64 } } - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - # --- Usernames: reserved + collectible --- - - /api/collectible-usernames: - get: - tags: ["Panel: Usernames"] - summary: List collectible usernames - description: Requires `usernames.read`. - security: [{ panelSession: [] }] - parameters: - - { name: status, in: query, schema: { type: string, enum: ["", vault, owned, burned] } } - - { name: owner_user_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: q, in: query, schema: { type: string } } - responses: - "200": - description: "OK — `next_before_id` is a string (int64 precision)." - content: - application/json: - schema: - type: object - properties: - rows: { type: array, items: { type: object, additionalProperties: true } } - has_more: { type: boolean } - next_before_id: { type: string } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/collectible-usernames/{id}: - get: - tags: ["Panel: Usernames"] - summary: Collectible username detail + transfer log - description: Requires `usernames.read`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - asset: { type: object, additionalProperties: true } - transfers: { type: array, items: { type: object, additionalProperties: true } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/reserved-usernames: - get: - tags: ["Panel: Usernames"] - summary: List reserved usernames - description: Requires `usernames.read`. Proxied to the Admin API's `GET /v1/reserved-usernames`. - security: [{ panelSession: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { $ref: "#/components/parameters/Limit" } - - { name: offset, in: query, schema: { type: integer } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/reserve-username: - post: - tags: ["Panel: Usernames"] - summary: Add a username to the operator reserved-username blocklist - description: Requires `usernames.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [username], properties: { username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/unreserve-username: - post: - tags: ["Panel: Usernames"] - summary: Remove a username from the operator reserved-username blocklist - description: Requires `usernames.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [username], properties: { username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/mint-collectible-username: - post: - tags: ["Panel: Usernames"] - summary: Mint a collectible username - description: Requires `usernames.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [username, currency, amount] - properties: - username: { type: string } - owner_user_id: { $ref: "#/components/schemas/FlexInt64" } - owner_channel_id: { $ref: "#/components/schemas/FlexInt64" } - currency: { type: string } - amount: { $ref: "#/components/schemas/FlexInt64" } - crypto_currency: { type: string } - crypto_amount: { $ref: "#/components/schemas/FlexInt64" } - url: { type: string } - purchase_date: { $ref: "#/components/schemas/FlexUnix" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/transfer-collectible-username: - post: - tags: ["Panel: Usernames"] - summary: Transfer a collectible username - description: Requires `usernames.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [username] - properties: - username: { type: string } - to_user_id: { $ref: "#/components/schemas/FlexInt64" } - to_channel_id: { $ref: "#/components/schemas/FlexInt64" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/revoke-collectible-username: - post: - tags: ["Panel: Usernames"] - summary: Revoke (or burn) a collectible username - description: >- - Requires `usernames.manage`. When `burn: true`, the effective - action name in the command envelope flips to - `burn-collectible-username` (same route/struct). - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [username], properties: { username: { type: string }, burn: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/delete-collectible-username: - post: - tags: ["Panel: Usernames"] - summary: Delete a collectible username - description: Requires `usernames.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [username], properties: { username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Official verification --- - - /api/verification/applications: - get: - tags: ["Panel: Official Verification"] - summary: List official-verification applications - description: Requires `verification.review`. - security: [{ panelSession: [] }] - parameters: - - { name: status, in: query, schema: { type: string } } - - { name: target_type, in: query, schema: { type: string } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: reviewer, in: query, schema: { type: string } } - - { name: q, in: query, schema: { type: string } } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/verification/applications/{id}: - get: - tags: ["Panel: Official Verification"] - summary: Application detail - description: Requires `verification.review`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/verification/counts: - get: - tags: ["Panel: Official Verification"] - summary: Application counts by status - description: Requires `verification.review`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/verification/applications/{id}/claim: - post: - tags: ["Panel: Official Verification"] - summary: Claim an application - description: Requires `verification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/verification/applications/{id}/approve: - post: - tags: ["Panel: Official Verification"] - summary: Approve an application (grants the badge) - description: Requires `verification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/verification/applications/{id}/reject: - post: - tags: ["Panel: Official Verification"] - summary: Reject an application - description: Requires `verification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/actions/revoke-verification: - post: - tags: ["Panel: Official Verification"] - summary: Strip an official-verification badge - description: Requires BOTH `verification.review` and `verification.revoke`. Addresses the target directly, not an application id. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [target_type, target_id] - properties: - target_type: { type: string, description: "domain.VerificationTargetType, validated" } - target_id: { $ref: "#/components/schemas/FlexInt64" } - internal_note: { type: string } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - # --- Bot verification (feature-flagged: 404s while TELESRV_HIDE_THIRD_PARTY_VERIFICATION=true, the default) --- - - /api/botverification/verifiers: - get: - tags: ["Panel: Bot Verification"] - summary: List third-party verifier bots - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - parameters: - - { $ref: "#/components/parameters/Limit" } - - { name: enabled_only, in: query, schema: { type: string, description: "1/true/yes/on" } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - "404": { description: "Third-party verification is disabled (TELESRV_HIDE_THIRD_PARTY_VERIFICATION)" } - - /api/botverification/icons: - get: - tags: ["Panel: Bot Verification"] - summary: List verification icon catalog - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - parameters: - - { $ref: "#/components/parameters/Limit" } - - { name: active_only, in: query, schema: { type: string } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/botverification/marks: - get: - tags: ["Panel: Bot Verification"] - summary: List active third-party marks - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - parameters: - - { name: peer_type, in: query, schema: { type: string, enum: ["", user, channel] } } - - { name: verifier_bot_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: q, in: query, schema: { type: string } } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/botverification/requests: - get: - tags: ["Panel: Bot Verification"] - summary: List third-party verification requests - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - parameters: - - { name: status, in: query, schema: { type: string } } - - { name: peer_type, in: query, schema: { type: string } } - - { name: verifier_bot_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: q, in: query, schema: { type: string } } - responses: - "200": { $ref: "#/components/responses/PanelCursorList" } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/botverification/requests/{id}: - get: - tags: ["Panel: Bot Verification"] - summary: Request detail - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - request: { type: object, additionalProperties: true } - verifier: { type: object, additionalProperties: true } - mark_active: { type: boolean } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/botverification/counts: - get: - tags: ["Panel: Bot Verification"] - summary: Request counts by status - description: Requires `botverification.review`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/botverification/requests/{id}/approve: - post: - tags: ["Panel: Bot Verification"] - summary: Approve a third-party verification request - description: Requires `botverification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/botverification/requests/{id}/reject: - post: - tags: ["Panel: Bot Verification"] - summary: Reject a third-party verification request - description: Requires `botverification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/botverification/requests/{id}/revoke: - post: - tags: ["Panel: Bot Verification"] - summary: Revoke a granted third-party verification - description: Requires `botverification.review`. - security: [{ panelSession: [], panelCsrf: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/PanelVersionedDecision" } - responses: - "200": { $ref: "#/components/responses/PanelActionOK" } - "403": { $ref: "#/components/responses/PanelForbidden" } - "409": { $ref: "#/components/responses/PanelConflict" } - - /api/actions/grant-bot-verifier: - post: - tags: ["Panel: Bot Verification"] - summary: Appoint a bot as a third-party verifier - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [bot_id, company_name] - properties: - bot_id: { $ref: "#/components/schemas/FlexInt64" } - icon_document_id: { $ref: "#/components/schemas/FlexInt64" } - company_name: { type: string } - default_description: { type: string } - can_modify_custom_description: { type: boolean } - version: { $ref: "#/components/schemas/FlexInt64" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-bot-verifier-enabled: - post: - tags: ["Panel: Bot Verification"] - summary: Enable/disable a verifier bot - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [bot_id, enabled], properties: { bot_id: { $ref: "#/components/schemas/FlexInt64" }, enabled: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/revoke-bot-verifier: - post: - tags: ["Panel: Bot Verification"] - summary: Strip a bot's verifier status - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [bot_id], properties: { bot_id: { $ref: "#/components/schemas/FlexInt64" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/upsert-verification-icon: - post: - tags: ["Panel: Bot Verification"] - summary: Add/edit a verification-icon catalog entry - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [document_id, name], properties: { document_id: { $ref: "#/components/schemas/FlexInt64" }, name: { type: string }, owner_bot_id: { $ref: "#/components/schemas/FlexInt64" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/set-verification-icon-active: - post: - tags: ["Panel: Bot Verification"] - summary: Enable/disable a verification icon - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [icon_id, active], properties: { icon_id: { $ref: "#/components/schemas/FlexInt64" }, active: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - /api/actions/revoke-custom-verification: - post: - tags: ["Panel: Bot Verification"] - summary: Strip a third-party mark from a peer - description: Requires `botverification.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [verifier_bot_id, peer_type, peer_id], properties: { verifier_bot_id: { $ref: "#/components/schemas/FlexInt64" }, peer_type: { type: string, enum: [user, channel] }, peer_id: { $ref: "#/components/schemas/FlexInt64" } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "502": { $ref: "#/components/responses/PanelActionUpstreamError" } } - - # --- Server settings --- - - /api/server/identity: - get: - tags: ["Panel: Server Settings"] - summary: Server identity (name/description/icon presence) - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/server/add-server-link: - get: - tags: ["Panel: Server Settings"] - summary: The ready-made owpg://addserver link for this server - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/server/icon: - get: - tags: ["Panel: Server Settings"] - summary: Current server icon (binary) - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - "404": { description: No icon set } - - /api/actions/set-server-identity: - post: - tags: ["Panel: Server Settings"] - summary: Set the server's name/description - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, properties: { name: { type: string }, description: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/actions/set-welcome-message-templates: - post: - tags: ["Panel: Server Settings"] - summary: Set the welcome (phone/email) message templates - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, properties: { phone_template: { type: string }, email_template: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/actions/set-login-code-message-template: - post: - tags: ["Panel: Server Settings"] - summary: Set the login-code SMS/message template - description: >- - Requires `server.manage`. The template must contain exactly one - `{{code}}` placeholder if non-empty, else 422 - (`domain.ValidateLoginCodeMessageTemplate`). - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, properties: { template: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" }, "422": { description: "template must contain exactly one {{code}}", content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } } } - - /api/actions/upload-server-icon: - post: - tags: ["Panel: Server Settings"] - summary: Upload a new server icon - description: "Requires `server.manage`. File ≤ 2MiB, extension in {.png,.jpg,.jpeg,.webp,.gif}." - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: { description: "JSON — common fields only", type: string } - file: { type: string, format: binary } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/actions/remove-server-icon: - post: - tags: ["Panel: Server Settings"] - summary: Remove the server icon - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/actions/complete-setup: - post: - tags: ["Panel: Server Settings"] - summary: Mark first-run setup as complete - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/server/env: - get: - tags: ["Panel: Server Settings"] - summary: Current .env values - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/update-server-env: - post: - tags: ["Panel: Server Settings"] - summary: Update .env values - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/PanelCommandFields" }, { type: object, required: [values], properties: { values: { type: object, additionalProperties: { type: string } } } }] - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/server/status: - get: - tags: ["Panel: Server Settings"] - summary: Server process status - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/server/docker-status: - get: - tags: ["Panel: Server Settings"] - summary: Live container/process status (Services tab) - description: >- - Requires `server.manage`. Backed by `procctl.Manager.DockerStatus`, - which shells out to `docker compose ps`. If the deployment actually - runs under podman (or `docker` isn't installed/running), this exec - fails and the tab renders as "nothing running" rather than a clear - error — the handler doesn't currently distinguish "no services - configured" from "couldn't ask Docker". - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/server/check-updates: - get: - tags: ["Panel: Server Settings"] - summary: Check for available server updates - description: Requires `server.manage`. - security: [{ panelSession: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "403": { $ref: "#/components/responses/PanelForbidden" } - - /api/actions/restart-server: - post: - tags: ["Panel: Server Settings"] - summary: Restart the server process - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - /api/actions/update-server: - post: - tags: ["Panel: Server Settings"] - summary: Update the server to the latest build - description: Requires `server.manage`. - security: [{ panelSession: [], panelCsrf: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/PanelCommandFields" } - responses: { "200": { $ref: "#/components/responses/PanelActionOK" }, "403": { $ref: "#/components/responses/PanelForbidden" } } - - # ============================================================ - # ADMIN API (internal/adminapi) — bearer token - # ============================================================ - - /healthz: - get: - tags: ["AdminAPI: Health"] - summary: Health check - security: [] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { status: { type: string, const: ok } } } } } } - - /v1/accounts/set-frozen: - post: - tags: ["AdminAPI: Accounts"] - summary: Freeze/unfreeze an account - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [user_id, frozen] - properties: - user_id: { type: integer, format: int64 } - frozen: { type: boolean } - freeze_until: { type: string, format: date-time } - freeze_appeal_url: { type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "400": { $ref: "#/components/responses/AdminApiCommandError" } } - - /v1/accounts/set-restricted: - post: - tags: ["AdminAPI: Accounts"] - summary: Restrict/unrestrict an account (spam sanction — join/message gate) - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [user_id, restricted] - properties: - user_id: { type: integer, format: int64 } - restricted: { type: boolean } - restricted_until: { type: string, format: date-time } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "400": { $ref: "#/components/responses/AdminApiCommandError" } } - - /v1/accounts/{id}/avatar: - get: - tags: ["AdminAPI: Accounts"] - summary: Account avatar (binary) - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } } - "404": { description: Not found } - - /v1/accounts/grant-premium: - post: - tags: ["AdminAPI: Accounts"] - summary: Grant Premium months - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id, months], properties: { user_id: { type: integer, format: int64 }, months: { type: integer } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-verified: - post: - tags: ["AdminAPI: Accounts"] - summary: Set/clear verified badge - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id, verified], properties: { user_id: { type: integer, format: int64 }, verified: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-flags: - post: - tags: ["AdminAPI: Accounts"] - summary: Set scam/fake flags - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, scam: { type: boolean }, fake: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-support: - post: - tags: ["AdminAPI: Accounts"] - summary: Set/clear support flag - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id, support], properties: { user_id: { type: integer, format: int64 }, support: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-username: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id, username], properties: { user_id: { type: integer, format: int64 }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-profile: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's first/last name - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, first_name: { type: string }, last_name: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-phone: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's phone - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id, phone], properties: { user_id: { type: integer, format: int64 }, phone: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-login-email: - post: - tags: ["AdminAPI: Accounts"] - summary: Set/clear a user's login email - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, email: { type: string, description: "empty clears it" } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-avatar: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's avatar (static image) - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + user_id (int64)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-avatar-video: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's avatar (animated video) - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + user_id (int64), video_start_ts (float64)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-color: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's profile/name color - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/PeerColorInput" }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/set-emoji-status: - post: - tags: ["AdminAPI: Accounts"] - summary: Set a user's emoji status - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/EmojiStatusInput" }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/accounts/revoke-sessions: - post: - tags: ["AdminAPI: Accounts"] - summary: Revoke a user's authorized sessions - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [user_id], properties: { user_id: { type: integer, format: int64 }, hash: { type: integer, format: int64 }, keep_hash: { type: integer, format: int64 }, revoke_all: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/{id}/avatar: - get: - tags: ["AdminAPI: Channels"] - summary: Channel avatar (binary) - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: { "200": { description: Image bytes, content: { image/*: { schema: { type: string, format: binary } } } }, "404": { description: Not found } } - - /v1/channels/set-avatar: - post: - tags: ["AdminAPI: Channels"] - summary: Set a channel's avatar - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + channel_id (int64)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-verified: - post: - tags: ["AdminAPI: Channels"] - summary: Set/clear verified badge - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [channel_id, verified], properties: { channel_id: { type: integer, format: int64 }, verified: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-flags: - post: - tags: ["AdminAPI: Channels"] - summary: Set scam/fake flags - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 }, scam: { type: boolean }, fake: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-settings: - post: - tags: ["AdminAPI: Channels"] - summary: Partially update channel settings - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [channel_id] - properties: - channel_id: { type: integer, format: int64 } - gigagroup: { type: [boolean, "null"] } - antispam: { type: [boolean, "null"] } - participants_hidden: { type: [boolean, "null"] } - noforwards: { type: [boolean, "null"] } - join_to_send: { type: [boolean, "null"] } - join_request: { type: [boolean, "null"] } - slowmode_seconds: { type: [integer, "null"] } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-username: - post: - tags: ["AdminAPI: Channels"] - summary: Set a channel's username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [channel_id, username], properties: { channel_id: { type: integer, format: int64 }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-color: - post: - tags: ["AdminAPI: Channels"] - summary: Set a channel's profile/name color - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/PeerColorInput" }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/channels/set-emoji-status: - post: - tags: ["AdminAPI: Channels"] - summary: Set a channel's emoji status - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [channel_id], properties: { channel_id: { type: integer, format: int64 } } }, { $ref: "#/components/schemas/EmojiStatusInput" }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/bots/create: - post: - tags: ["AdminAPI: Bots & Broadcasts"] - summary: Create a bot account - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [owner_user_id, name, username], properties: { owner_user_id: { type: integer, format: int64 }, name: { type: string }, username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/broadcasts/create: - post: - tags: ["AdminAPI: Bots & Broadcasts"] - summary: Create a broadcast - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [message, target_mode], properties: { message: { type: string }, target_mode: { type: string }, user_ids: { type: array, items: { type: integer, format: int64 } } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/bots/delete: - post: - tags: ["AdminAPI: Bots & Broadcasts"] - summary: Delete a bot account - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [bot_user_id], properties: { bot_user_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/bots/export-token: - post: - tags: ["AdminAPI: Bots & Broadcasts"] - summary: Export a bot's auth token - description: Requires scoped permission `bots.token.read` (or master token). - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [bot_user_id], properties: { bot_user_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/messages/delete: - post: - tags: ["AdminAPI: Messages"] - summary: Delete specific private messages - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [owner_user_id, peer, ids] - properties: - owner_user_id: { type: integer, format: int64 } - peer: { $ref: "#/components/schemas/Peer" } - ids: { type: array, items: { type: integer } } - revoke: { type: boolean } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/messages/delete-history: - post: - tags: ["AdminAPI: Messages"] - summary: Delete a private chat's history - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [owner_user_id, peer] - properties: - owner_user_id: { type: integer, format: int64 } - peer: { $ref: "#/components/schemas/Peer" } - max_id: { type: integer } - min_date: { type: integer } - max_date: { type: integer } - just_clear: { type: boolean } - revoke: { type: boolean } - max_batches: { type: integer } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/set-archived: - post: - tags: ["AdminAPI: Stickers"] - summary: Archive/unarchive a sticker set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [set_id, archived], properties: { set_id: { type: integer, format: int64 }, archived: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/set-sort-order: - post: - tags: ["AdminAPI: Stickers"] - summary: Reorder a sticker set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [set_id, sort_order], properties: { set_id: { type: integer, format: int64 }, sort_order: { type: integer } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/rename: - post: - tags: ["AdminAPI: Stickers"] - summary: Rename a sticker set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [set_id, title], properties: { set_id: { type: integer, format: int64 }, title: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/delete: - post: - tags: ["AdminAPI: Stickers"] - summary: Delete a sticker set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [set_id], properties: { set_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/create: - post: - tags: ["AdminAPI: Stickers"] - summary: Create a sticker set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + title, short_name, kind, emoji, keywords (opt.)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/add: - post: - tags: ["AdminAPI: Stickers"] - summary: Add a sticker to a set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + set_id (int64), emoji, keywords (opt.)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/remove: - post: - tags: ["AdminAPI: Stickers"] - summary: Remove a sticker from a set - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [set_id, document_id], properties: { set_id: { type: integer, format: int64 }, document_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/stickers/documents/{id}/animation: - get: - tags: ["AdminAPI: Stickers"] - summary: Sticker animation - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: { "200": { description: Animation bytes }, "400": { $ref: "#/components/responses/AdminApiCodedError" }, "404": { $ref: "#/components/responses/AdminApiCodedError" } } - - /v1/gif-catalog/create: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Add a GIF catalog entry - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - multipart/form-data: - schema: - type: object - required: [file, metadata] - properties: - file: { type: string, format: binary } - metadata: { description: "JSON — CommandMeta + title, content_sha256 (opt.)", type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/set-enabled: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Enable/disable a GIF catalog entry - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [id, enabled], properties: { id: { type: integer, format: int64 }, enabled: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/set-sort-order: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Reorder a GIF catalog entry - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [id, sort_order], properties: { id: { type: integer, format: int64 }, sort_order: { type: integer } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/set-category: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Set a GIF catalog entry's category - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [id, category], properties: { id: { type: integer, format: int64 }, category: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/auto-categorize: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Auto-categorize uncategorized GIFs - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/CommandMeta" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/delete-uncategorized: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Delete every uncategorized GIF catalog entry - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: { $ref: "#/components/schemas/CommandMeta" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/delete: - post: - tags: ["AdminAPI: GIF Catalog"] - summary: Delete a GIF catalog entry - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [id], properties: { id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/gif-catalog/documents/{id}/preview: - get: - tags: ["AdminAPI: GIF Catalog"] - summary: GIF preview - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: { "200": { description: Preview bytes }, "400": { $ref: "#/components/responses/AdminApiCodedError" }, "404": { $ref: "#/components/responses/AdminApiCodedError" } } - - /v1/emoji/{id}/animation: - get: - tags: ["AdminAPI: Health"] - summary: Custom emoji Lottie animation - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: { "200": { description: "Animation JSON bytes (Content-Type forced to application/json)" }, "400": { $ref: "#/components/responses/AdminApiCodedError" }, "404": { $ref: "#/components/responses/AdminApiCodedError" } } - - /v1/storage/manual-purge: - post: - tags: ["AdminAPI: Storage"] - summary: Manually purge storage by category - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [categories] - properties: - categories: - type: array - items: { type: string, enum: [photo, video, round_video, gif, music, voice, file] } - include_avatars: { type: boolean } - created_before: { type: [string, "null"], format: date-time, description: "RFC3339; null = no age filter" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/moderation/cases: - get: - tags: ["AdminAPI: Moderation"] - summary: List moderation cases - security: [{ adminApiBearer: [] }] - parameters: - - { name: limit, in: query, schema: { type: integer, default: 50 } } - - { name: assigned_to, in: query, schema: { type: string } } - - { name: statuses, in: query, description: comma-separated, schema: { type: string } } - - { name: target_type, in: query, schema: { type: string } } - - { name: target_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_updated_at, in: query, schema: { type: string, format: date-time } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { cases: { type: array, items: { type: object, additionalProperties: true } } } } } } } - - /v1/moderation/cases/{id}: - get: - tags: ["AdminAPI: Moderation"] - summary: Moderation case detail - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: "domain.ModerationCaseDetail (Decisions/Actions/Appeals nil-normalized to [])", content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "404": { $ref: "#/components/responses/AdminApiModerationError" } - - /v1/moderation/reports/{id}: - get: - tags: ["AdminAPI: Moderation"] - summary: Moderation report detail - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": { description: "domain.ModerationReport", content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "404": { $ref: "#/components/responses/AdminApiModerationError" } - - /v1/moderation/cases/{id}/claim: - post: - tags: ["AdminAPI: Moderation"] - summary: Claim a moderation case - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [expected_version, actor] - properties: { expected_version: { type: integer, format: int64 }, actor: { type: string } } - responses: - "200": { description: "domain.ModerationCase", content: { application/json: { schema: { type: object, additionalProperties: true } } } } - "409": { $ref: "#/components/responses/AdminApiModerationError" } - - /v1/moderation/cases/{id}/decide: - post: - tags: ["AdminAPI: Moderation"] - summary: Decide a moderation case - description: >- - `kind` = domain.ModerationDecisionKind. `actions[].kind` includes - `restrict_account`/`unrestrict_account` (the account spam-restriction - sanction), `freeze_account`/`unfreeze_account`, `mark_scam`, - `mark_fake`, `clear_peer_flags`, `delete_private_message`, - `delete_channel_message`, `delete_account`. - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [expected_version, actor, reason, kind] - properties: - expected_version: { type: integer, format: int64 } - actor: { type: string } - reason: { type: string } - command_id: { type: string } - kind: { type: string, description: "domain.ModerationDecisionKind" } - actions: - type: array - items: - type: object - properties: - kind: { type: string, description: "domain.ModerationActionKind, e.g. restrict_account" } - payload: { type: object, additionalProperties: true } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { created: { type: boolean }, case: { type: object, additionalProperties: true } } } } } } - "400": { $ref: "#/components/responses/AdminApiModerationError" } - "409": { $ref: "#/components/responses/AdminApiModerationError" } - - /v1/moderation/cases/{id}/appeals: - post: - tags: ["AdminAPI: Moderation"] - summary: Submit an appeal on a moderation case - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [appellant_user_id, text] - properties: { appellant_user_id: { type: integer, format: int64 }, text: { type: string } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { created: { type: boolean }, appeal: { type: object, additionalProperties: true } } } } } } - - /v1/moderation/cases/{id}/appeals/{appeal_id}/review: - post: - tags: ["AdminAPI: Moderation"] - summary: Review an appeal - security: [{ adminApiBearer: [] }] - parameters: - - { $ref: "#/components/parameters/PathId" } - - { name: appeal_id, in: path, required: true, schema: { type: integer, format: int64 } } - requestBody: - required: true - content: - application/json: - schema: - type: object - required: [expected_version, actor, reason, granted] - properties: - expected_version: { type: integer, format: int64 } - actor: { type: string } - reason: { type: string } - command_id: { type: string } - granted: { type: boolean, description: "maps to ModerationDecisionAppealGrant/Deny" } - actions: - type: array - items: { type: object, properties: { kind: { type: string }, payload: { type: object, additionalProperties: true } } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { created: { type: boolean }, case: { type: object, additionalProperties: true } } } } } } - "409": { $ref: "#/components/responses/AdminApiModerationError" } - - /v1/collectible-usernames/mint: - post: - tags: ["AdminAPI: Collectible Usernames"] - summary: Mint a collectible username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [username, currency, amount] - properties: - username: { type: string } - owner_user_id: { type: string, description: "int64 as decimal string; at most one owner_*" } - owner_channel_id: { type: string, description: "int64 as decimal string" } - currency: { type: string } - amount: { type: string, description: "int64 as decimal string" } - crypto_currency: { type: string } - crypto_amount: { type: string, description: "int64 as decimal string" } - url: { type: string } - purchase_date: { type: integer, format: int64, description: "unix seconds" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/collectible-usernames/transfer: - post: - tags: ["AdminAPI: Collectible Usernames"] - summary: Transfer a collectible username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [username], properties: { username: { type: string }, to_user_id: { type: string, description: "int64 as decimal string" }, to_channel_id: { type: string, description: "int64 as decimal string" } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/collectible-usernames/revoke: - post: - tags: ["AdminAPI: Collectible Usernames"] - summary: Revoke (or burn) a collectible username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [username], properties: { username: { type: string }, expected_owner_user_id: { type: string, description: "int64 as decimal string" }, burn: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/collectible-usernames/delete: - post: - tags: ["AdminAPI: Collectible Usernames"] - summary: Delete a collectible username - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [username], properties: { username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/collectible-usernames: - get: - tags: ["AdminAPI: Collectible Usernames"] - summary: List collectible usernames - description: All int64 ids/amounts in the response are decimal strings, to avoid float64 precision loss. - security: [{ adminApiBearer: [] }] - parameters: - - { name: status, in: query, schema: { type: string } } - - { name: q, in: query, schema: { type: string } } - - { name: owner_user_id, in: query, schema: { type: integer, format: int64 } } - - { name: owner_channel_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { assets: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "400": { $ref: "#/components/responses/AdminApiCollectibleError" } - - /v1/collectible-usernames/{id}: - get: - tags: ["AdminAPI: Collectible Usernames"] - summary: Collectible username detail + transfer log - security: [{ adminApiBearer: [] }] - parameters: - - { $ref: "#/components/parameters/PathId" } - - { $ref: "#/components/parameters/Limit" } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - asset: { type: object, additionalProperties: true } - transfers: { type: array, items: { type: object, additionalProperties: true } } - "404": { $ref: "#/components/responses/AdminApiCollectibleError" } - - /v1/reserved-usernames/reserve: - post: - tags: ["AdminAPI: Reserved Usernames"] - summary: Add a username to the operator reserved-username blocklist - description: >- - `Username` is sent with a **capitalized, untagged** JSON key - (`{"Username": "..."}`, not `username`) — a source-level quirk - (missing `json:` struct tag), not a spec error. Every other request - body in this document uses lowercase `snake_case` keys. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [Username], properties: { Username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/reserved-usernames/unreserve: - post: - tags: ["AdminAPI: Reserved Usernames"] - summary: Remove a username from the operator reserved-username blocklist - description: Same `Username` (capitalized, untagged) quirk as `/v1/reserved-usernames/reserve`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [Username], properties: { Username: { type: string } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" } } - - /v1/reserved-usernames: - get: - tags: ["AdminAPI: Reserved Usernames"] - summary: List reserved usernames - security: [{ adminApiBearer: [] }] - parameters: - - { name: q, in: query, schema: { type: string } } - - { $ref: "#/components/parameters/Limit" } - - { name: offset, in: query, schema: { type: integer } } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - reserved: - type: array - items: - type: object - properties: - username: { type: string } - reason: { type: string } - actor: { type: string } - created_at: { type: integer, format: int64, description: unix seconds } - "500": { description: "list failed", content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } } - - /v1/verification/applications: - get: - tags: ["AdminAPI: Official Verification"] - summary: List official-verification applications - security: [{ adminApiBearer: [] }] - parameters: - - { name: target_type, in: query, schema: { type: string } } - - { name: reviewer, in: query, schema: { type: string } } - - { name: q, in: query, schema: { type: string } } - - { name: status, in: query, description: comma-separated, schema: { type: string } } - - { $ref: "#/components/parameters/Limit" } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { applications: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/verification/applications/{id}: - get: - tags: ["AdminAPI: Official Verification"] - summary: Application detail - security: [{ adminApiBearer: [] }] - parameters: - - { $ref: "#/components/parameters/PathId" } - - { name: limit, in: query, description: events, schema: { type: integer } } - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - application: { type: object, additionalProperties: true } - events: { type: array, items: { type: object, additionalProperties: true } } - target: { type: object, additionalProperties: true } - target_error: { type: string } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/verification/counts: - get: - tags: ["AdminAPI: Official Verification"] - summary: Application counts by status - security: [{ adminApiBearer: [] }] - responses: - "200": { description: OK, content: { application/json: { schema: { type: object, properties: { counts: { type: object, additionalProperties: { type: string } } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/verification/applications/{id}/claim: - post: - tags: ["AdminAPI: Official Verification"] - summary: Claim an application - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiVerificationError" } } - - /v1/verification/applications/{id}/approve: - post: - tags: ["AdminAPI: Official Verification"] - summary: Approve an application (grants the badge) - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiVerificationError" } } - - /v1/verification/applications/{id}/reject: - post: - tags: ["AdminAPI: Official Verification"] - summary: Reject an application - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiVerificationError" } } - - /v1/verification/revoke: - post: - tags: ["AdminAPI: Official Verification"] - summary: Strip an official-verification badge - description: Requires BOTH `verification.review` and `verification.revoke` (or master token). Addresses the target directly, not an application id. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [target_type, target_id] - properties: - target_type: { type: string, description: "domain.VerificationTargetType" } - target_id: { type: integer, format: int64 } - internal_note: { type: string } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/verifiers: - get: - tags: ["AdminAPI: Bot Verification"] - summary: List third-party verifier bots - security: [{ adminApiBearer: [] }] - parameters: - - { $ref: "#/components/parameters/Limit" } - - { name: enabled_only, in: query, schema: { type: string, description: "1/true/yes/on" } } - responses: - "200": { description: "PascalCase fields: BotID, IconDocumentID, CompanyName, ... (id/version fields as decimal strings)", content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/botverification/icons: - get: - tags: ["AdminAPI: Bot Verification"] - summary: List verification icon catalog - security: [{ adminApiBearer: [] }] - parameters: - - { $ref: "#/components/parameters/Limit" } - - { name: active_only, in: query, schema: { type: string } } - responses: - "200": { description: "PascalCase fields: ID, DocumentID, OwnerBotID, Name, Active, ...", content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/botverification/marks: - get: - tags: ["AdminAPI: Bot Verification"] - summary: List active third-party marks - security: [{ adminApiBearer: [] }] - parameters: - - { name: peer_type, in: query, schema: { type: string, enum: [user, channel] } } - - { name: verifier_bot_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: q, in: query, schema: { type: string } } - responses: - "200": { description: "PascalCase fields: ID, VerifierBotID, PeerType, PeerID, ...", content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } }, has_more: { type: boolean }, next_before_id: { type: string } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/botverification/requests: - get: - tags: ["AdminAPI: Bot Verification"] - summary: List third-party verification requests - security: [{ adminApiBearer: [] }] - parameters: - - { name: peer_type, in: query, schema: { type: string } } - - { name: status, in: query, description: comma-separated, schema: { type: string } } - - { name: verifier_bot_id, in: query, schema: { type: integer, format: int64 } } - - { name: before_id, in: query, schema: { type: integer, format: int64 } } - - { $ref: "#/components/parameters/Limit" } - - { name: q, in: query, schema: { type: string } } - responses: - "200": { description: "PascalCase fields incl. ApplicantUserID, PeerTitle/Username, Reason, Status, InternalNote, ...", content: { application/json: { schema: { type: object, properties: { rows: { type: array, items: { type: object, additionalProperties: true } }, has_more: { type: boolean }, next_before_id: { type: string } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/botverification/requests/{id}: - get: - tags: ["AdminAPI: Bot Verification"] - summary: Request detail - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - responses: - "200": - description: OK - content: - application/json: - schema: - type: object - properties: - request: { type: object, additionalProperties: true } - verifier: { type: object, additionalProperties: true, description: "present even if verifier is gone" } - verifier_error: { type: string } - mark_active: { type: boolean } - mark_error: { type: string } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - "404": { $ref: "#/components/responses/AdminApiBotVerificationError" } - - /v1/botverification/counts: - get: - tags: ["AdminAPI: Bot Verification"] - summary: Request counts by status - security: [{ adminApiBearer: [] }] - responses: - "200": { description: "counts for pending/approved/rejected/revoked, zero-filled", content: { application/json: { schema: { type: object, properties: { counts: { type: object, additionalProperties: { type: string } } } } } } } - "403": { $ref: "#/components/responses/AdminApiForbidden" } - - /v1/botverification/requests/{id}/approve: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Approve a third-party verification request - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiBotVerificationError" } } - - /v1/botverification/requests/{id}/reject: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Reject a third-party verification request - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiBotVerificationError" } } - - /v1/botverification/requests/{id}/revoke: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Revoke a granted third-party verification - security: [{ adminApiBearer: [] }] - parameters: [{ $ref: "#/components/parameters/PathId" }] - requestBody: { $ref: "#/components/requestBodies/AdminApiVersionedNote" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" }, "409": { $ref: "#/components/responses/AdminApiBotVerificationError" } } - - /v1/botverification/verifiers/grant: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Appoint a bot as a third-party verifier - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [bot_id, company_name, version] - properties: - bot_id: { type: integer, format: int64 } - icon_document_id: { type: integer, format: int64 } - company_name: { type: string } - default_description: { type: string } - can_modify_custom_description: { type: boolean } - version: { type: integer, format: int64, description: "0 = new grant" } - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/verifiers/set-enabled: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Enable/disable a verifier bot - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [bot_id, enabled], properties: { bot_id: { type: integer, format: int64 }, enabled: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/verifiers/revoke: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Strip a bot's verifier status - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [bot_id], properties: { bot_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/icons/upsert: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Add/edit a verification-icon catalog entry - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [document_id, name], properties: { document_id: { type: integer, format: int64 }, name: { type: string }, owner_bot_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/icons/set-active: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Enable/disable a verification icon - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [icon_id, active], properties: { icon_id: { type: integer, format: int64 }, active: { type: boolean } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - - /v1/botverification/marks/revoke: - post: - tags: ["AdminAPI: Bot Verification"] - summary: Strip a third-party mark from a peer - description: Requires `botverification.manage`. - security: [{ adminApiBearer: [] }] - requestBody: - required: true - content: - application/json: - schema: - allOf: [{ $ref: "#/components/schemas/CommandMeta" }, { type: object, required: [verifier_bot_id, peer_type, peer_id], properties: { verifier_bot_id: { type: integer, format: int64 }, peer_type: { type: string, enum: [user, channel] }, peer_id: { type: integer, format: int64 } } }] - responses: { "200": { $ref: "#/components/responses/AdminApiCommandOK" }, "403": { $ref: "#/components/responses/AdminApiForbidden" } } - -components: - securitySchemes: - panelSession: - type: apiKey - in: cookie - name: telesrv_admin_session - description: Signed session cookie issued by POST /api/login. 12h default TTL, no server-side store. - panelCsrf: - type: apiKey - in: header - name: X-CSRF-Token - description: >- - Must match the telesrv_admin_csrf cookie's value. Required on every - mutating panel request except POST /api/login. Origin header is also - checked when present. - adminApiBearer: - type: http - scheme: bearer - description: >- - The master token (TELESRV_ADMIN_API_TOKEN, implicit "*" permission) - or a scoped token (TELESRV_ADMIN_SCOPED_TOKENS) carrying specific - permissions. Compared with subtle.ConstantTimeCompare against every - configured token unconditionally, to avoid timing side-channels. - - parameters: - PathId: - name: id - in: path - required: true - schema: { type: integer, format: int64 } - Limit: - name: limit - in: query - schema: { type: integer } - - requestBodies: - PanelVersionedDecision: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [version] - properties: - version: { $ref: "#/components/schemas/FlexInt64" } - internal_note: { type: string } - PanelUserAvatarUpload: - required: true - content: - multipart/form-data: - schema: - type: object - required: [metadata, file] - properties: - metadata: - description: "JSON — common fields + user_id (int64)" - type: string - file: { type: string, format: binary } - PanelAdminUserAction: - description: >- - Shared by create/update-access/set-password for a named admin - operator account. `reason` is mandatory here specifically - (`decodeAdminUserAction` 400s on blank reason), stricter than the - generic action envelope. - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - properties: - id: { type: integer, format: int64 } - username: { type: string } - password: { type: string } - permissions: { type: array, items: { type: string } } - enabled: { type: [boolean, "null"] } - PanelSetFrozen: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/PanelCommandFields" - - type: object - required: [user_id, frozen] - properties: - user_id: { type: integer, format: int64 } - frozen: { type: boolean } - freeze_until: { type: string, format: date-time } - freeze_appeal_url: { type: string } - AdminApiVersionedNote: - required: true - content: - application/json: - schema: - allOf: - - $ref: "#/components/schemas/CommandMeta" - - type: object - required: [version] - properties: - version: { type: integer, format: int64 } - internal_note: { type: string } - - responses: - PanelUnauthorized: - description: >- - Missing/expired session (`{"error":"not authenticated"}`), or a - named-account session invalidated server-side since it was issued — - password changed, permissions edited, or account disabled — caught - on every request by re-checking `admin_console_users` against the - session's `epoch` claim (`{"error":"session is no longer valid"}`). - content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } - PanelForbidden: - description: >- - Missing/mismatched CSRF cookie or header, Origin mismatch (plain - `ErrorEnvelope`), or an authenticated session lacking the required - permission (`PermissionErrorEnvelope`, adds a `permission` field — - `{"error":"permission accounts.manage is required","code":"FORBIDDEN","permission":"accounts.manage"}`). - content: { application/json: { schema: { $ref: "#/components/schemas/PermissionErrorEnvelope" } } } - PanelNotFound: - description: Not found - content: { application/json: { schema: { $ref: "#/components/schemas/ErrorEnvelope" } } } - PanelConflict: - description: >- - Optimistic-locking conflict — another reviewer already decided this - row, or `version` is stale. Only the verification/bot-verification - decision routes preserve this status from the upstream Admin API; - every other action route flattens upstream errors to 502 instead - (see `PanelActionUpstreamError`). - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - PanelActionOK: - description: >- - Command accepted. Response body is the same `CommandResult` shape - the Admin API returns — the panel relays it as-is from - `callAdminAPI`/`callAdminMultipart`/`callAdminCommand`, or builds an - equivalent `CommandResult` locally for the handful of actions that - operate on local state (admin operators, server settings) instead - of calling through to the Admin API. - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - PanelActionUpstreamError: - description: >- - The upstream Admin API call failed (transport error, or the command - itself failed). `writeCommandResultAPI` answers this as HTTP 502 - with `CommandResult{Status:"failed", Error: }` — - NOT the upstream's own status code. Verification and bot-verification - decision routes are the exception: they preserve the real upstream - status (404/409/429/400) instead of flattening to 502. - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - PanelCursorList: - description: Cursor-paginated list - content: - application/json: - schema: - type: object - properties: - query: { type: string } - limit: { type: integer } - rows: { type: array, items: { type: object, additionalProperties: true } } - has_more: { type: boolean } - next_before_id: { type: integer, format: int64 } - listing: { type: object, additionalProperties: true } - AdminApiCommandOK: - description: >- - Command result. Success is HTTP 200; a failed command (err != nil) - is answered as HTTP 400 (not 500) by the shared writeCommandResult - helper, with the same CommandResult body shape (Status:"failed"). - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - AdminApiCommandError: - description: Command failed (validation or execution error) - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - AdminApiForbidden: - description: Missing bearer token, or token lacks the required permission - content: { application/json: { schema: { $ref: "#/components/schemas/PermissionErrorEnvelope" } } } - AdminApiCodedError: - description: Coded error envelope - content: { application/json: { schema: { $ref: "#/components/schemas/CodedErrorEnvelope" } } } - AdminApiModerationError: - description: Moderation-family coded error (404/403/409/429/400/500 depending on admin.Code*) - content: { application/json: { schema: { $ref: "#/components/schemas/CodedErrorEnvelope" } } } - AdminApiCollectibleError: - description: Collectible-username coded error (404/409/400/500 depending on admin.Code*) - content: { application/json: { schema: { $ref: "#/components/schemas/CodedErrorEnvelope" } } } - AdminApiVerificationError: - description: >- - Official-verification coded error. Status derives from - admin.VerificationErrorCode: NotFound→404; - Conflict/TargetOccupied/TargetVerified→409; - {StatusInvalid,ReasonRequired,TargetInvalid,TargetNotPublic,TargetRestricted,TargetSystem,NotOwner,UserTargetsDisabled,Invalid}→400; - default→400 (forced down from 500 on the command path). On conflict, - `message` is overwritten with a "reload and decide again" prompt. - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - AdminApiBotVerificationError: - description: >- - Bot-verification coded error. Status derives from - admin.BotVerificationErrorCode: *NotFound codes→404; - {Conflict,Limit,RequestExists}→409; RateLimited→429; - {Forbidden,DescriptionForbidden,Invalid,IconInactive,IconInvalid,StatusInvalid,ReasonRequired,TargetInvalid,TargetSystem,VerificationInvalid}→400 - (explicitly 400 not 403 — the refusal is about the *subject* bot/peer, - not the caller's authorization); default→400. - content: { application/json: { schema: { $ref: "#/components/schemas/CommandResult" } } } - - schemas: - ErrorEnvelope: - type: object - required: [error] - properties: - error: { type: string } - code: { type: string } - CodedErrorEnvelope: - allOf: - - $ref: "#/components/schemas/ErrorEnvelope" - - type: object - required: [code] - PermissionErrorEnvelope: - allOf: - - $ref: "#/components/schemas/ErrorEnvelope" - - type: object - properties: - permission: { type: string, description: "present on 403s from a missing-permission check" } - - PanelCommandFields: - type: object - description: Common fields on every Panel API POST /api/actions/* (and moderation/verification decision) body. - required: [reason, confirm] - properties: - command_id: { type: string, description: "Idempotency key; server-generated if omitted." } - reason: { type: string, description: "Mandatory audit reason." } - confirm: { type: boolean, description: "Operator confirmation; must be true." } - - FlexInt64: - description: >- - Accepts either a JSON number or a numeric JSON string (custom - UnmarshalJSON on the Go side, `flexInt64`) — used for Telegram-scale - ids where a bare JSON number risks float64 precision loss in JS. - Distinct from a `json:",string"`-tagged field (see field - descriptions), which requires a JSON string specifically. - oneOf: - - { type: integer, format: int64 } - - { type: string } - FlexUnix: - description: >- - Accepts a JSON number (unix seconds), or a string — either a numeric - unix-seconds string or an RFC3339 date (`flexUnix`). - oneOf: - - { type: integer, format: int64, description: unix seconds } - - { type: string, description: "numeric unix-seconds string, or RFC3339" } - - CommandMeta: - type: object - description: Common fields embedded in nearly every Admin API (internal/adminapi) request body. - properties: - command_id: { type: string, description: "Idempotency key." } - actor: { type: string, description: "Auto-filled from the scoped-token principal name if omitted." } - reason: { type: string } - dry_run: { type: boolean } - - CommandResult: - type: object - description: Response shape for nearly every Admin API POST route. - properties: - command_id: { type: string } - action: { type: string } - status: { type: string } - already_executed: { type: boolean } - dry_run: { type: boolean } - target_user_id: { type: integer, format: int64 } - target_peer: { $ref: "#/components/schemas/Peer" } - message: { type: string } - details: { type: object, additionalProperties: true } - error: { type: string } - - Peer: - type: object - properties: - Type: { type: string, description: "domain.PeerType, e.g. user/channel/chat" } - ID: { type: integer, format: int64 } - - PeerColorInput: - type: object - description: Shared shape for every *-set-color request (accounts/channels, both APIs). - properties: - for_profile: { type: boolean } - has_color: { type: boolean } - color: { type: integer } - background_emoji_id: { type: integer, format: int64, description: "sent with json:\",string\" on the Admin API side" } - - EmojiStatusInput: - type: object - description: Shared shape for every *-set-emoji-status request (accounts/channels, both APIs). - properties: - document_id: { type: integer, format: int64, description: "sent with json:\",string\" on the Admin API side" } - until: { type: integer, description: unix seconds } - - AccountRow: - type: object - description: >- - `cmd/telesrv-admin/readstore.go` AccountRow — fields are NOT JSON-tagged, - so they serialize with Go's default PascalCase field names, not - snake_case like the rest of this API. - properties: - ID: { type: integer, format: int64 } - Phone: { type: string } - Username: { type: string } - FirstName: { type: string } - LastName: { type: string } - Collectibles: - type: array - description: Collectible usernames in display order; distinct from the editable Username slot. - items: - type: object - properties: { Username: { type: string }, Active: { type: boolean } } - CreatedAt: { type: string, format: date-time } - UpdatedAt: { type: string, format: date-time } - Frozen: { type: boolean } - Reason: { type: string, description: "The freeze reason given when Frozen was last set true. Empty if never frozen." } - Verified: { type: boolean } - Scam: { type: boolean } - Fake: { type: boolean } - PremiumUntil: { type: integer, format: int64 } - LastActiveAt: { type: string, format: date-time } - DeviceCount: { type: integer } - LoginEmail: { type: string } diff --git a/docs/reserved-usernames.md b/docs/reserved-usernames.md deleted file mode 100644 index 8d6db064..00000000 --- a/docs/reserved-usernames.md +++ /dev/null @@ -1,73 +0,0 @@ -# Suggested reserved usernames - -A starter blocklist for `TELESRV_RESERVED_USERNAMES` (see -`internal/config/config.go`, `ReservedUsernames`), beyond the shipped -default (`owpengram, admin, administrator, support, staff, moderator, -official, root, owner`). The mechanism blocks these, case-insensitively, -from self-service `account.updateUsername` / `channels.updateUsername` — -an operator can still assign one deliberately from the admin panel, since -that's not the squatting this exists to stop. - -Organized by why each entry is worth blocking. Trim or extend per your own -deployment — none of this is enforced by the server itself, it's just a -starting list to feed into the env var. - -## Official-sounding / staff-impersonation - -Names that would read as "this account speaks for the operator" if a -random user grabbed them. - -```text -telegram, telegramofficial, telegramsupport, telegramteam, telegramstaff, -telegramhq, telegramnews, telegramnotifications, telegramverify, telegrambot, -telegramservice, security, securityteam, verify, verification, verified, -helpdesk, helpcenter, customerservice, customersupport, servicedesk, service, -system, systemmessage, systembot, notifications, notification, alerts, -announcements, announcement, newsroom, press, pressoffice, spambot, antispam, -abuse, abusereport, reports, legal, compliance, billing, payments, finance, -accounts, accountservices, recovery, unlock, restore, backup, headquarters, -staffteam, modteam, moderation, moderators, founder, cofounder, management -``` - -## Founders / executives (real people this server is not affiliated with) - -Blocks an account from presenting as Telegram's actual leadership. - -```text -durov, pavel, paveldurov, pdurov, nikolai, nikolaidurov, ndurov, durovpavel, -telegramceo, telegramfounder -``` - -## Crypto / scam-bait - -The classic "free crypto giveaway" impersonation targets — official-sounding -project/exchange names, plus generic scam-pattern handles. - -```text -bitcoin, ethereum, toncoin, tonwallet, tonfoundation, crypto, cryptowallet, -wallet, airdrop, giveaway, bonus, freecoins, claim, claimnow, binance, -coinbase, kraken, metamask, trustwallet, ledger, opensea, uniswap, elonmusk, -tesla, spacex, cryptosupport, walletsupport, tonsupport, binancesupport, -investment, investmentteam, forextrading, tradingbot, signals, cryptosignals -``` - -## Suggested env var - -Comma-separated, matching `envListOr`'s parsing: - -```bash -TELESRV_RESERVED_USERNAMES=owpengram,admin,administrator,support,staff,moderator,official,root,owner,telegram,telegramofficial,telegramsupport,telegramteam,telegramstaff,telegramhq,telegramnews,telegramnotifications,telegramverify,telegrambot,telegramservice,security,securityteam,verify,verification,verified,helpdesk,helpcenter,customerservice,customersupport,servicedesk,service,system,systemmessage,systembot,notifications,notification,alerts,announcements,announcement,newsroom,press,pressoffice,spambot,antispam,abuse,abusereport,reports,legal,compliance,billing,payments,finance,accounts,accountservices,recovery,unlock,restore,backup,headquarters,staffteam,modteam,moderation,moderators,founder,cofounder,management,durov,paveldurov,pdurov,nikolai,nikolaidurov,ndurov,durovpavel,telegramceo,telegramfounder,bitcoin,ethereum,toncoin,tonwallet,tonfoundation,crypto,cryptowallet,wallet,airdrop,giveaway,bonus,freecoins,claim,claimnow,binance,coinbase,kraken,metamask,trustwallet,ledger,opensea,uniswap,elonmusk,tesla,spacex,cryptosupport,walletsupport,tonsupport,binancesupport,investment,investmentteam,forextrading,tradingbot,signals,cryptosignals -``` - -## What this doesn't cover - -- **Collectible usernames** (`internal/admin/service.go` collectible-username - actions) go through the admin panel/Admin API directly and aren't gated - by this list at all — an operator minting one bypasses it on purpose. -- **Prefix/substring squatting** (`telegram_real`, `xtelegram`, `te1egram`) - — the blocklist is an exact-match (case-insensitive) list, not a pattern - matcher, so lookalike spellings need their own entries if you want them - blocked too. -- This is a *username* blocklist only — it says nothing about display - names, bios, or profile photos, which can impersonate just as easily and - aren't restricted by `TELESRV_RESERVED_USERNAMES` at all. diff --git a/internal/app/channels/service.go b/internal/app/channels/service.go index 7d64d788..f6ddba08 100644 --- a/internal/app/channels/service.go +++ b/internal/app/channels/service.go @@ -635,19 +635,11 @@ func (s *Service) AdminSetSettings(ctx context.Context, channelID int64, patch d return s.channels.SetChannelAdminSettings(ctx, channelID, patch) } -// AdminSetUsername force-sets or clears a channel username through the admin -// path. Unlike UpdateUsername it does not consult config.ReservedUsernames -- -// an operator who deliberately reserved a word still needs to be able to hand -// it to a specific channel -- but format/length validity is still enforced, -// just against the lower minChannelUsernameLenAdmin floor. +// AdminSetUsername force-sets or clears a channel username through the admin path. func (s *Service) AdminSetUsername(ctx context.Context, channelID int64, username string) (domain.Channel, error) { if s == nil || s.channels == nil || channelID == 0 { return domain.Channel{}, domain.ErrChannelInvalid } - username = normalizeChannelUsername(username) - if username != "" && !validChannelUsernameMinLen(username, minChannelUsernameLenAdmin) { - return domain.Channel{}, domain.ErrUsernameInvalid - } return s.channels.SetChannelUsernameAdmin(ctx, channelID, username) } @@ -2528,20 +2520,8 @@ func normalizeChannelUsername(username string) string { return strings.TrimSpace(username) } -// minChannelUsernameLenAdmin is the floor for an operator-assigned channel -// username via AdminSetUsername (the admin API/panel) -- shorter than what -// self-service channels.updateUsername allows, mirroring -// users.minUsernameLenAdmin: a deliberate short handle assigned by an -// operator is not the squatting the higher self-service minimum guards -// against. -const minChannelUsernameLenAdmin = 3 - func validChannelUsername(username string) bool { - return validChannelUsernameMinLen(username, 5) -} - -func validChannelUsernameMinLen(username string, minLen int) bool { - if len(username) < minLen || len(username) > 32 { + if len(username) < 5 || len(username) > 32 { return false } for i := 0; i < len(username); i++ { diff --git a/internal/app/users/service.go b/internal/app/users/service.go index 3da6c874..b3e7fe87 100644 --- a/internal/app/users/service.go +++ b/internal/app/users/service.go @@ -116,13 +116,7 @@ func WithReservedUsernames(names []string) Option { } const ( - minUsernameLen = 5 - // minUsernameLenAdmin is the floor for an operator-assigned username via - // UpdateUsernameAdmin (the admin API/panel) -- shorter than what self-service - // account.updateUsername allows, since a deliberate short handle assigned by - // an operator (e.g. a 3-4 char official/brand account) is not the squatting - // the higher self-service minimum guards against. - minUsernameLenAdmin = 3 + minUsernameLen = 5 maxUsernameLen = 32 maxProfileNameRunes = 64 // bio 长度双档,对齐 appConfig about_length_limit_default=70 / @@ -389,13 +383,7 @@ func (s *Service) updateUsername(ctx context.Context, userID int64, username str return s.projectOne(ctx, self.ID, self) } if username != "" { - minLen := minUsernameLen - if !enforceReserved { - // enforceReserved=false is exactly the admin-bypass path - // (UpdateUsernameAdmin) -- see minUsernameLenAdmin's doc comment. - minLen = minUsernameLenAdmin - } - if !validUsernameMinLen(username, minLen) || (enforceReserved && s.reserved.Contains(username)) { + if !validUsername(username) || (enforceReserved && s.reserved.Contains(username)) { return domain.User{}, domain.ErrUsernameInvalid } var ( @@ -1052,11 +1040,7 @@ func normalizeUsername(username string) string { } func validUsername(username string) bool { - return validUsernameMinLen(username, minUsernameLen) -} - -func validUsernameMinLen(username string, minLen int) bool { - if len(username) < minLen || len(username) > maxUsernameLen { + if len(username) < minUsernameLen || len(username) > maxUsernameLen { return false } for i := 0; i < len(username); i++ {