Skip to content
Fluxer API

HTTP API

Use the HTTP API to read and change resources. Discover the base URL through /.well-known/fluxer. Third-party clients use endpoints.api_public, and the first-party web application uses endpoints.api_client.

Use /v1, the only API version.

Read resource limits from instance discovery. Attachment counts, expression counts and profile field lengths can differ between deployments.

Field notation explains optional, nullable, and nullish fields.

Send JSON bodies with Content-Type: application/json. Responses are UTF-8 JSON unless an operation states otherwise. Message operations and webhook execution also accept multipart bodies, and OAuth2 token operations accept form bodies.

An overloaded instance returns 503 SERVICE_UNAVAILABLE with Retry-After: 1. Wait before retrying.

Unknown paths and unsupported methods return 404 NOT_FOUND without an Allow header. Trailing slashes are significant.

Every GET route supports HEAD, returning the same status and headers without a body. Routes without GET do not support HEAD. The same-host origin requirement also applies to HEAD.

Each operation documents its accepted body format and fields.

Use application/json for JSON and application/x-www-form-urlencoded for OAuth2 form bodies. OAuth2 token operations also accept multipart/form-data.

Create message, Modify message and Execute webhook accept these multipart fields:

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.

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.

Include the multipart boundary in Content-Type and each field name in Content-Disposition. Malformed multipart bodies return FAILED_TO_PARSE_MULTIPART_FORM_DATA. Unknown fields and non-file values for valid files[n] indices are ignored.

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.

This normalisation applies to JSON and form bodies, query strings, path parameters, request headers, and cookies.

A nested object containing only null values also becomes null. The root object never becomes null, even when it is empty or has only null values. An empty body is treated as {} and validated for required fields. Malformed JSON returns 400 INVALID_FORM_BODY with a validation error at path body and code INVALID_FORMAT.

Those operations document their own JSON validation errors.

Authentication defines the accepted Authorization schemes and links to the OAuth2 scope registry. Each operation states which credentials it accepts. The sudo verification object defines the proof required for 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. 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?1stringRequest body media type and, for multipart bodies, the boundary
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 Use the media type specified in 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 too-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, ETag
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 again, 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. A route that is not exempt from the global bucket also counts against one global bucket. 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.

The paths below 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. A request whose Origin is absent or outside the allow-list receives *. For an allowed origin, the policy replaces * with that exact origin and sends Vary: Origin.

Access-Control-Expose-Headers is the value X-Fluxer-Version, ETag. Every other Fluxer response header, the rate limit headers and X-Request-ID included, is hidden from cross-origin script. Access-Control-Allow-Headers is Content-Type, Authorization, X-Requested-With, Accept-Language, X-Request-ID, If-None-Match, so a cross-origin client revalidates an ETag it was served.

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 constraints with the same code 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