Skip to content
Fluxer API

HTTP API

The Fluxer HTTP API is the set of routes a client calls to read and change data. Every route is published under /v1, and /v1 is the only version. The base URL comes from the instance discovery document, which is served unversioned at /.well-known/fluxer. A third-party client reads endpoints.api_public there, and the first-party web application reads endpoints.api_client.

Every route is also mounted at the root, so a path resolves with or without the prefix. A client MUST use the /v1 form. Download stored object is the one exception, and it resolves at the root alone.

Numeric limits such as attachment counts, expression counts, and profile field lengths are instance configuration. Each deployment publishes its current values in its instance discovery document, so a client reads them at runtime.

API conventions defines the wire notation and the omission and null semantics.

A JSON request body has Content-Type: application/json. A response body is UTF-8 JSON with Content-Type: application/json unless the operation states another representation. Message operations and webhook execution accept multipart bodies, and the OAuth2 token operations accept form-encoded bodies.

The API applies no generic byte limit to a request body. An operation that accepts an upload bounds that upload itself.

Every request counts against one in-flight request ceiling for the whole instance. A request that arrives while the instance is at that ceiling returns 503 SERVICE_UNAVAILABLE with Retry-After: 1 before the operation runs. The /_health, /_healthz, and /_metrics probe paths are exempt.

A request to a path that matches no route returns 404 NOT_FOUND. A request whose path is registered but not for the request method is answered the same way, and the response has no Allow header. Routing is strict, so a trailing slash is significant.

The GET registered for a path also serves HEAD. A path that registers no GET serves no HEAD either. A HEAD response has the status and headers that GET returns, with no body. The request still reports HEAD as its method, so the same-host origin check can refuse a HEAD that has no Origin where the identical GET succeeds.

An operation documents its body under JSON body, Form body, or Multipart body and states any content-type restriction it enforces.

A JSON body is parsed from the raw request text without inspecting Content-Type. The instance content filter scans a POST, PUT, or PATCH body that parses as JSON against the banned-phrase and banned-URL blocklists, whatever the header declares. It skips a body whose Content-Type contains multipart/form-data or application/x-www-form-urlencoded. A client MUST send the canonical media type.

Form bodies accept application/x-www-form-urlencoded and multipart/form-data interchangeably. The three OAuth2 token operations are the only ones that take one. A field that occurs once is a string or file. Repeating the same field name produces an array in occurrence order, and a name ending in [] also collects its values into an array.

Create message, Modify message, and Execute webhook are the only operations that define a multipart body of their own. Each selects the multipart parser when the request Content-Type contains multipart/form-data and parses the body as JSON otherwise. The three OAuth2 token operations also accept multipart/form-data, but read it as an ordinary form body.

FieldTypeDescription
payload_json?1stringJSON object with the operation’s complete message request payload
files[n]?2fileDirect attachment file at zero-based index n

1 An absent payload_json is read as an empty object. A value that is not a string, or does not parse as JSON, is rejected with INVALID_JSON_IN_PAYLOAD_JSON

2 n is a run of decimal digits, at most 10000 and below the deployment’s max_attachments_per_message limit, which defaults to 10

The legacy names file and file followed by an index are accepted as file fields as well, and a bare file takes the next free legacy index.

Five field-name failures are rejected with their own code. An index outside either bound returns FILE_INDEX_EXCEEDS_MAXIMUM. Any other name beginning with files[ returns INVALID_FILE_FIELD_NAME. Two file fields resolving to the same index return DUPLICATE_FILE_INDEX. More than one file supplied for one index returns MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED. Where the resolved limit is 0, any file field at all returns ATTACHMENTS_NOT_ALLOWED_FOR_MESSAGE.

The Content-Type header supplies the multipart boundary. Each part’s field name is in Content-Disposition. A body the multipart parser cannot read is rejected with FAILED_TO_PARSE_MULTIPART_FORM_DATA. A field name the operation does not recognise is ignored, and a files[n] part whose value is not a file is ignored once its index has been bounds-checked.

A multipart message body MAY also have content, nonce, tts, flags, favorite_meme_id, and sticker_ids as plain form fields. Each overrides the member of the same name in the parsed payload_json, and sticker_ids collects every value it is given.

The attachments array inside payload_json maps attachment metadata to files by matching each attachment id to n in files[n]. A metadata entry that has a filename but matches no supplied file is rejected with NO_FILE_FOR_ATTACHMENT_METADATA, and two metadata entries claiming the same file index are rejected with DUPLICATE_ATTACHMENT_IDS_NOT_ALLOWED. A supplied file that no metadata entry claims is still attached, taking its index as the attachment ID and its uploaded filename as the attachment filename. A metadata entry that has no filename and matches no supplied file is read as a pre-uploaded attachment reference.

Messages defines the attachment metadata and pre-uploaded attachment form, and Attachment uploads defines the separate relay upload flow.

Field tables use the shared wire table notation.

The shared validator normalises the JSON body, a form body, the query string, path parameters, request headers, and cookies before their schemas run.

A nested object whose members have all become null becomes null in turn. The root object itself is never collapsed this way. An empty request body is read as an empty object, so the caller sees the operation’s own required-field failures. A body that is present but does not parse as JSON returns 400 INVALID_FORM_BODY with one element at path body and code INVALID_FORMAT.

An empty string stays an empty string and an empty nested object stays an empty object in those three. The first two reject a JSON body that does not parse. On a JSON body all three collapse every schema failure to one validation entry, and each operation names that entry on its own page.

Authentication defines the accepted Authorization schemes, their exact token forms, and the OAuth2 scope registry. An operation that requires a credential states the scheme on its resource page. The sudo verification object defines the sudo mode credential that guards sensitive account operations.

An OAuth2 bearer access token is accepted only where a route opts in, and the resource page says so. Everywhere else a bearer credential is refused with 403 ACCESS_DENIED, and an account with a suspicious activity flag is refused with 403 ACCOUNT_SUSPICIOUS_ACTIVITY.

These headers are accepted across resources. An operation-specific header is documented in that operation’s request header table.

FieldTypeDescription
Authorization?stringThe single credential for an authenticated request, in one of the accepted schemes
Content-Type?1stringThe media type of the request body, which selects the multipart parser when it contains multipart/form-data
Accept-Language?2stringSelects the locale used for an error message
X-Audit-Log-Reason?3stringFree-text reason recorded on the resulting audit log entry
X-Fluxer-Client-Properties?4stringBase64-encoded JSON with the native client’s os, read when an authentication session is created
X-Fluxer-Sudo-Mode-JWT?5stringA sudo mode proof previously issued to the authenticated user
X-Captcha-Token?6stringThe CAPTCHA solution issued by the selected provider
X-Captcha-Type?6stringEither hcaptcha or turnstile, selecting the provider that issued the token
X-Request-ID?7stringA correlation identifier the client chooses, echoed unchanged in the response
User-Agent?stringThe originating client description recorded on a new authentication session and on an Admin audit entry
Origin?8stringThe browser origin used for cross-origin negotiation and for the mutating same-host origin check

1 Only the multipart message operations read it to decide how the body is parsed. The instance content filter reads it separately, as described under request body formats

2 The configured locale of the authenticated account takes precedence, so this header selects the locale only for an unauthenticated request or an account with no configured locale

3 The value is read verbatim with no percent-decoding, then stripped of form feed and right-to-left override characters and trimmed. A blank or over-long value is treated as absent

4 Read only for a native Fluxer User-Agent, at most 4096 characters, and only the os member is used

5 A valid token replaces the sudo proof fields in the operation body and is echoed in the response header without extending its lifetime.

6 Read only while the instance has a provider configured and the operation is gated. The handshake is defined in CAPTCHA handling

7 A supplied value is echoed back unchanged and unvalidated

8 The exact use is defined in cross-origin requests

A client MUST NOT send an X-Request-ID it is not willing to see in logs, because Fluxer echoes and records the value without validating it.

An X-Audit-Log-Reason normalised to more than 512 characters is discarded, and the request still succeeds. Every request is normalised this way, but only an operation that supports an audit reason records the result.

FieldTypeDescription
X-Fluxer-Version?1stringThe build version of the instance that served the request
X-Request-ID?2stringThe identifier assigned to the request
X-Fluxer-Sudo-Mode-JWT?3stringThe sudo mode token for the caller, present only on an operation that completes a sudo proof
Content-Type?stringThe media type of the representation, absent from a response with no body
Cache-Control?4stringThe literal value no-cache unless the operation sets its own directive
Access-Control-Allow-Origin?5stringThe request Origin when it is a configured application origin, and the literal * on routes that set their own wildcard
Access-Control-Expose-Headers?5stringThe literal value X-Fluxer-Version
Vary?5stringThe literal value Origin, sent whenever the allowed origin was echoed
Retry-After?6stringWhole seconds to wait, sent on a rate limit denial, a slowmode denial, a resource lock, and the in-flight ceiling 503
X-RateLimit-Limit?7stringPresent on a route denial and on a successful bot or webhook request
X-RateLimit-Remaining?7stringPresent on a route denial and on a successful bot or webhook request
X-RateLimit-Reset?7stringUnix timestamp in seconds
X-RateLimit-Reset-After?7stringSeconds until the bucket resets
X-RateLimit-Bucket?7stringA stable 16-character hash of the bucket name
X-RateLimit-Scope?7stringPresent on a rate limit denial, taking the value user, shared, or global
X-RateLimit-Global?7stringPresent on a global rate limit denial, taking the literal value true

1 The value is the literal dev when the build has no version stamp

2 A generated UUID unless the request supplied its own, in which case that value is echoed back unchanged and unvalidated

3 A token newly issued where the caller proved sudo mode afresh, and otherwise the incoming proof echoed back with no extension of its lifetime

4 Absent from a response with no body

5 Access-Control-Expose-Headers is sent on every response the instance CORS policy handles. Access-Control-Allow-Origin and Vary are sent only for an allowed origin

6 The literal 1 on the in-flight ceiling 503, and otherwise the rate limit header contract

7 The complete contract is defined in Rate limits

An operation that sets its own Cache-Control keeps that value. A response whose Content-Type names a stylesheet, script, font, image, video, or audio representation receives public, max-age=31536000.

Every route consumes its own rate limit bucket and is additionally evaluated against one global bucket unless that bucket is exempt. A denial returns 429 RATE_LIMITED. Rate limits defines the bucket scoping rules, the global allowance, the 429 body, the scope registry, and the complete X-RateLimit-* header contract.

A global denial has Retry-After, X-RateLimit-Scope, and X-RateLimit-Global alone.

A small set of routes exists only on the hosted Fluxer deployment. A self-hosted deployment answers one of them with 404 NOT_FOUND. Deployment availability lists every hosted-only route and states how a client resolves the deployment kind before authenticating.

The CORS response policy is an allow-list of exactly two origins, the deployment’s configured web application endpoint and its marketing endpoint. A request whose Origin matches one of them receives Access-Control-Allow-Origin set to that origin and Vary: Origin. Every other request, including one that sends no Origin, receives no Access-Control-Allow-Origin from that policy. Credentialed cross-origin requests are not enabled, so Access-Control-Allow-Credentials is never sent.

Five paths are readable from any origin. /v1/webhooks/{webhook_id}/{token} and /v1/webhooks/{webhook_id}/{token}/messages/{message_id} have a second cross-origin policy that allows any origin. Four of the methods registered on them refuse the first-party web client outright, and that refusal is defined by Origin refusal.

Get instance discovery on /.well-known/fluxer, Get OpenAPI document on /v1/openapi.json, and Get client geolocation on /v1/ip set Access-Control-Allow-Origin: * in the operation itself. The wildcard stands for any origin outside the allow-list, and for an allowed origin the policy replaces it with that exact origin and sends Vary: Origin.

Access-Control-Expose-Headers is the single value X-Fluxer-Version. Every other Fluxer response header, the rate limit headers and X-Request-ID included, is hidden from cross-origin script.

A JSON error response identifies the stable error code and a human-readable message. An operation can add further top-level members with structured detail. Errors documents the code registries, the HTTP status fallback mapping, and the localisation behaviour.

FieldTypeDescription
code1stringStable machine-readable API error code
message2stringHuman-readable description of this failure
errors?3array[validation error object]Request fields that failed validation

1 Every code is an uppercase symbolic name matching [A-Z][A-Z0-9_]*

2 The wording varies by locale and defaults to the code itself when the failure supplies no message

3 Present on an INVALID_FORM_BODY response, and on any other failure that has field detail

{
"code": "MISSING_PERMISSIONS",
"message": "Missing permissions"
}

The body has no request identifier. A client correlates a failure through the X-Request-ID response header.

MISSING_OAUTH_SCOPE has required_scope, and RATE_LIMITED has retry_after and global, where global is true only on a global bucket denial. Each operation that produces one of those members documents it. A client MUST treat any member it does not recognise as absent. Two operations returning the same code can have different extra members, so a client MUST read the members its own operation documents.

Each entry identifies one failed input field. A 400 response whose top-level code is INVALID_FORM_BODY has the array in errors. Errors states which failures produce it.

FieldTypeDescription
path1stringThe location of the request field that failed validation
code?2stringStable machine-readable validation code
message3stringThe human-readable description of this validation failure

1 A nested field is a dot-joined path such as embeds.0.title, and a failure with no field position uses the literal path root

2 Present whenever the failure has an enumerated code from the validation error code registry

3 The localised message registered for the code, falling back to the code itself, and a fixed English string on a failure that has no code

{
"code": "INVALID_FORM_BODY",
"message": "Invalid form body",
"errors": [
{"path": "name", "code": "BASE_TYPE_REQUIRED", "message": "This field is required"}
]
}

Fluxer produces at most one entry for each distinct pair of path and code, so a field that fails several equivalent constraints appears once.

PageCovers
ErrorsThe error envelope, status fallback mapping, and the API and validation code registries
Deployment availabilityRoutes that exist only on the hosted deployment
Authentication operationsRegistration, login, MFA, WebAuthn, SSO, email verification, recovery, sessions, IP authorisation, desktop handoff
GatewayBot discovery of the main Gateway endpoint and session start budget
InstanceInstance discovery, client geolocation, the limit key registry, the served OpenAPI document
OAuth2Authorisation, consent, token exchange, introspection, revocation, granted authorisations
ApplicationsApplication, bot account, client secret, and bot token management, plus public lookup
ConnectionsExternal account connection initiation, verification, visibility, and deletion
UsersThe user object and the shared user enumerations
Current userCurrent account retrieval, profile mutation, lifecycle, policy acceptance, authorised IP state
User settingsAccount, notification, and privacy settings, guild folders, voice activity sharing
User settings ProtobufEvery structured client preference message and enumeration
Email and password changesThe ticketed credential replacement flows
Multi-factor authenticationTOTP, backup codes, WebAuthn credentials, sudo verification
Phone verificationOutbound and inbound phone verification
RelationshipsFriend requests, friendships, blocks, relationship nicknames
User notesPrivate notes attached to user IDs
Private channelsDirect message and group DM discovery, creation, preload, pin state
User content collectionsRecent mentions, saved messages, asynchronous message deletion
Gift inventoryPremium gift codes created by the current account
Data harvestsData harvest creation, status, and download
Read statesMessage watermark and mention count acknowledgements
MemesThe saved image, video, and audio collection and batch GIF URL resolution
ThemesShareable custom CSS theme creation
ChannelsChannel objects, private recipients, permission overwrites, slowmode, RTC regions
CallsCall eligibility, region selection, ringing, and termination
StreamsGo Live stream keys, stream regions, preview image lifecycle
Entrance soundsThe entrance sound collection, its per-scope selections, and playback
MessagesMessages, attachments, embeds, history, acknowledgements, pins, reactions, typing
GuildsGuild objects and lifecycle operations
Guild channelsListing, creation, hierarchy, permission inheritance, bulk positioning
Guild membersMember objects, profiles, voice moderation, ownership, removal, role assignment
Guild member searchIndexed search filters, sorting, pagination, supplemental join metadata
Guild moderationBans, temporary bans, ban replacement, and the blocks a ban has
Guild emojisGuild emoji objects, uploads, metadata changes, deletion
Guild stickersGuild sticker objects, uploads, metadata changes, deletion
ExpressionsEmoji and sticker metadata reads across both
Guild audit logsAudit entries, typed targets, contexts, changes, filters, the audit reason contract
Roles and permissionsPermission flags, computation order, role objects and lifecycle
DiscoveryPublic guild listings, categories, the listing application lifecycle, joining without an invite
InvitesInvite lookup, creation, acceptance, deletion, code generation
WebhooksWebhook management, message execution, GitHub, Slack, and Instatus callbacks
SearchAuthenticated global message search
UnfurlAuthenticated external URL metadata resolution
BillingStripe checkout, card preapproval, gift purchase, age verification, refunds, the Stripe webhook
PremiumPremium pricing, entitlement state, subscription self-service, billing portal handoff
GiftsPublic gift code lookup and authenticated redemption
DonationsDonation currencies and intervals, checkout sessions, the donor management link
ReportsAuthenticated safety reports and email-verified Digital Services Act notices