Skip to content

HTTP API

Use the HTTP API to read and change resources. Every route lives under one base URL:

https://tebhos.com/api/v1

Paths on these pages are written relative to that base, so GET /channels/{channel_id} means GET https://tebhos.com/api/v1/channels/{channel_id}. A client that needs to discover the base URL, or any other endpoint of the deployment, reads /.well-known/tebhos first. The endpoints.api_public value there is the API root (it ends in /api), and the client appends /v1 itself.

v1 is the only API version. There is no version negotiation header and no older version to fall back to.

Resource limits such as attachment counts, message length, and guild expression counts are published in the limit configuration of instance discovery. Read them from there instead of hard-coding them.

Conventions

Snowflakes

Every ID is a snowflake: a 64-bit integer that the API always serialises as a decimal string, for example "1501314428688998182". Send IDs back as strings too. A JavaScript client MUST NOT parse an ID into a number, because a snowflake does not fit in a double-precision float.

Snowflakes encode their creation time and sort chronologically, which is what the cursor pagination below relies on.

Timestamps

Timestamps are ISO 8601 / RFC 3339 strings in UTC, for example "2026-09-23T00:15:57.000Z". A few fields that come from the realtime layer or from a cache carry Unix time instead, and those are called out on their own page (for example exp and iat in OAuth2 introspection).

Field notation

Field tables on every resource page use the same notation:

Notation Meaning
field? The field is optional: it can be omitted from a request, or absent from a response
?type The value is nullable: the field is present but can be null
field? with ?type The field can be absent and can also be null

A client MUST treat an unknown response field as absent rather than failing, because fields are added over time.

Pagination

List routes that can grow without bound are cursor-paginated with snowflakes. Where a route supports it, pass before, after, or around with a snowflake ID and limit with a count; the route page states the default and maximum limit. Small, bounded collections (for example connections or the applications a user owns) are returned whole.

Request format

Send JSON bodies with Content-Type: application/json. Responses are UTF-8 JSON unless an operation states otherwise. Exceptions are called out on their resource page:

A body that is not valid JSON, or whose shape does not match the operation (a missing required field, a wrong type), is rejected with 400 before the operation runs.

Unknown paths return 404 with an empty body. A known path called with a method it does not support also returns 404 with an empty body, not 405. Trailing slashes are significant: /users/@me/ is a different path from /users/@me.

Only the public media routes (/files/{key}, /avatars/..., /banners/..., /server-icons/..., and the other image routes described in Media proxy routes) answer HEAD. On every other route, HEAD is treated like any other unsupported method.

Authentication

Most routes require an Authorization header. TEBHOS accepts two schemes on the REST API:

Scheme Example Used by
Bearer Authorization: Bearer <session token> A signed-in user. The session token is returned by login and the other sign-in operations
Bot Authorization: Bot <application_id>.<secret> A bot account. The token is returned when the application is created

A user session token may also be sent bare, without the Bearer prefix. Authentication describes both credentials in full.

Each route states which credentials it accepts. Many user-facing routes are user-only: they read a session token and reject a valid bot token with 403 ACCESS_DENIED (an invalid one with 401). Routes that bots use (messages, reactions, channels, guild reads, and so on) accept both. OAuth2 bearer access tokens are accepted only by the two OAuth2 routes that document them (user information and current authorisation) and by a few read routes that require a matching scope; OAuth2 access tokens lists them. Any other route answers a valid access token with 403 ACCESS_DENIED.

Sensitive account operations additionally require sudo mode: a fresh proof of the password, an authenticator code, or a passkey, supplied in the request body or granted to the session for five minutes.

Standard request headers

Field Type Description
Authorization? string The credential, in one of the accepted schemes
Content-Type? string The request body media type, and for multipart bodies the boundary
Accept-Language? string Read by registration to seed the new account's locale. Error messages are not localised by this header
X-Captcha-Token? string The CAPTCHA solution, on the operations that are CAPTCHA-gated (see CAPTCHA handling)
X-Captcha-Type? string hcaptcha or turnstile, selecting the provider that issued the token. Omit it to use the instance's configured provider
X-Tebhos-Platform? string The native client platform, read by desktop handoff. The TEBHOS desktop app sends desktop
X-Tebhos-Features? string A comma-separated list of client capabilities. Currently only view_channel_members_permission is recognised, by role updates
User-Agent? string Recorded on a new authentication session and shown in the session list. Truncated to 255 characters

No audit-log reason header

TEBHOS does not read an audit-log reason from a request header. Operations that record a reason take it from their documented request fields.

Standard response headers

Field Type Description
Content-Type? string The media type of the body, absent from an empty response
Cache-Control? string Set by operations that need it, for example no-cache on instance discovery and public, max-age=31536000 on public media
Retry-After? string Whole seconds to wait, sent on every 429 and on a slowmode denial
X-RateLimit-Limit? string The bucket capacity, sent on a 429 from a route bucket
X-RateLimit-Remaining? string Always 0 on a 429 from a route bucket
X-RateLimit-Reset? string Unix time in seconds when the bucket is fully drained
X-RateLimit-Reset-After? string Seconds, with a fractional part, until the bucket resets
X-RateLimit-Bucket? string A stable 16-character hash of the bucket name
X-RateLimit-Scope? string user, shared, or global, sent on every 429
X-RateLimit-Global? string The literal true, sent only on a global 429
X-Total-Count? string The total count, on the operations that report one (for example the older /servers/{guild_id}/members list described under TEBHOS member object)

The X-RateLimit-* headers are sent only on a rate-limit denial, not on successful responses. Rate limits defines the full contract.

Rate limits

Every authenticated /api/v1 request counts against a global limit of 50 requests per second per client. The client is identified by its Authorization header, or by its IP address when it sends none. Unauthenticated GET/HEAD requests, public media reads, and webhook execution are exempt from the global limit.

Most routes also have their own route bucket, documented on each operation. A denial from either returns 429 with code RATE_LIMITED:

{
  "error": "429 Too Many Requests",
  "code": "RATE_LIMITED",
  "message": "...",
  "global": false,
  "retry_after": 1.25
}

retry_after is in seconds with a fractional part; Retry-After is the same value rounded up to whole seconds. Rate limits lists the buckets and the header contract.

Cross-origin requests

Browser requests are allowed from the TEBHOS web origins (https://tebhos.com and its *.tebhos.com subdomains). For an allowed Origin, the response carries Access-Control-Allow-Origin set to that origin and Access-Control-Allow-Credentials: true. Requests from any other origin receive no Access-Control-Allow-Origin, so a browser blocks them.

The preflight allows the methods GET, POST, PUT, PATCH, DELETE, and OPTIONS, and the request headers Content-Type and Authorization. The rate-limit headers and Retry-After are exposed to script through Access-Control-Expose-Headers.

Public media routes (avatars, icons, banners, and /files/{key}) answer every origin with Access-Control-Allow-Origin: *, so images can be loaded from anywhere.

A bot or server-side client is not a browser and is not affected by CORS.

Error response object

A failed request returns a JSON body with a stable error code and a human-readable message.

Structure

Field Type Description
error string The HTTP status line, for example "403 Forbidden"
code string Stable machine-readable error code
message string Human-readable description of the failure
errors? array[validation error object] The failing fields, on an INVALID_FORM_BODY response that has field detail

A client MUST branch on code and the HTTP status, never on message. Messages are written for end users, are currently in Turkish, and can change at any time.

Example

{
  "error": "403 Forbidden",
  "code": "FORBIDDEN",
  "message": "Kendi yetkilerinizi değiştiremezsiniz"
}

Some operations add further top-level members with structured detail, and document them. For example, login returns IP_AUTHORIZATION_REQUIRED with ticket, email, and resend_available_in, and a rate-limit denial carries global and retry_after. A client MUST ignore members it does not recognise.

Two older envelopes

A few account-security routes (the MFA login completions and the session routes on Authentication) report their own failures as {"error": "<CODE>", "message": "..."}, with the code in error and no code member. The OAuth2 routes put the OAuth2 error name (invalid_grant, invalid_client, ...) in message. Both are called out on their pages.

Validation error object

Each entry identifies one failed input field.

Structure

Field Type Description
path string The request field that failed, as a dot-joined path such as embeds.0.title
code string Stable machine-readable validation code
message string Description of the failure

Example

{
  "error": "400 Bad Request",
  "code": "INVALID_FORM_BODY",
  "message": "Kanal sesli kanal olmalı",
  "errors": [
    {"path": "channel_id", "code": "CHANNEL_MUST_BE_VOICE", "message": "CHANNEL_MUST_BE_VOICE"}
  ]
}

In an errors entry, message currently repeats the code. Many validation failures return INVALID_FORM_BODY with a top-level message only and no errors array. Errors lists the codes.

Resource pages

Page Covers
Errors The error envelope and the error code registry
Deployment availability Features that depend on how the instance is configured
Instance Instance discovery: endpoints, features, limits, public configuration
Authentication operations Registration, login, MFA completion, passkeys, SSO, password recovery, IP authorisation, sessions, desktop handoff
Gateway Bot discovery of the Gateway URL and session start values
OAuth2 Authorisation, consent, token exchange, introspection, revocation, granted authorisations
Applications Applications, bot accounts, client secrets, bot tokens, public lookup
Connections Linked external accounts: domains and OAuth providers
Users The user object and shared user enumerations
Current user The current account and its profile
User settings Account, notification, and privacy settings
User settings Protobuf Structured client preference messages
Email and password changes The code-verified credential change flows
Multi-factor authentication TOTP, backup codes, passkeys, sudo mode
Phone verification Phone number verification
Relationships Friends, friend requests, blocks
User notes Private notes about other users
Private channels Direct messages and group DMs
User content collections Mentions, saved messages, bulk deletion
Gift inventory Gift codes created by the current account
Data harvests Account data export
Read states Read markers and mention counts
Memes The saved media collection
GIFs GIF search through the instance provider
Themes Shareable custom CSS themes
Channels Channel objects, recipients, permission overwrites
Calls DM call ringing and control
Streams Go Live streams and previews
Entrance sounds Voice entrance sounds
Messages Messages, attachments, embeds, pins, reactions
Guilds Guild objects and lifecycle
Guild channels Channel listing, creation, ordering
Guild members Member objects, roles, voice moderation
Guild member search Member search and filtering
Guild moderation Bans and timeouts
Guild emojis Custom emojis
Guild stickers Custom stickers
Expressions Emoji and sticker lookups across guilds
Guild audit logs Audit log entries and filters
Roles and permissions Permission flags and role objects
Discovery Public guild listings
Invites Invite lookup, creation, acceptance
Webhooks Webhook management and execution
Search Message search
Unfurl Link preview resolution
Billing Checkout through Polar
Premium Premium plans and subscription state
Gifts Gift code lookup and redemption
Experiments Feature flags for the current account