initial commit

This commit is contained in:
Hampus Kraft
2026-01-01 20:42:59 +00:00
commit 2f557eda8c
9029 changed files with 1490197 additions and 0 deletions

28
fluxer_docs/Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM node:24-bookworm-slim AS base
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y --no-install-recommends libvips libvips-dev curl openssl && rm -rf /var/lib/apt/lists/*
RUN corepack enable
FROM base AS deps
WORKDIR /usr/src/app
COPY package.json pnpm-lock.yaml ./
COPY source.config.ts ./
RUN pnpm install --frozen-lockfile
FROM base AS build
WORKDIR /usr/src/app
COPY --from=deps /usr/src/app/node_modules ./node_modules
COPY . ./
RUN pnpm run build
FROM base AS runner
WORKDIR /usr/src/app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=build /usr/src/app/.next ./.next
COPY --from=build /usr/src/app/public ./public
COPY --from=build /usr/src/app/package.json ./package.json
COPY --from=deps /usr/src/app/node_modules ./node_modules
USER nobody
EXPOSE 3000
CMD ["node", "node_modules/next/dist/bin/next", "start", "-p", "3000"]

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {DocsBody, DocsDescription, DocsPage, DocsTitle} from 'fumadocs-ui/layouts/docs/page';
import {createRelativeLink} from 'fumadocs-ui/mdx';
import type {Metadata} from 'next';
import {notFound} from 'next/navigation';
import {getPageImage, source} from '@/lib/source';
import {getMDXComponents} from '@/mdx-components';
export default async function Page(props: PageProps<'/[[...slug]]'>) {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
const MDX = page.data.body;
return (
<DocsPage toc={page.data.toc} full={page.data.full}>
<DocsTitle>{page.data.title}</DocsTitle>
<DocsDescription>{page.data.description}</DocsDescription>
<DocsBody>
<MDX
components={getMDXComponents({
a: createRelativeLink(source, page),
})}
/>
</DocsBody>
</DocsPage>
);
}
export async function generateStaticParams() {
return source.generateParams();
}
export async function generateMetadata(props: PageProps<'/[[...slug]]'>): Promise<Metadata> {
const params = await props.params;
const page = source.getPage(params.slug);
if (!page) notFound();
return {
title: page.data.title,
description: page.data.description,
openGraph: {
images: getPageImage(page).url,
},
};
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {DocsLayout} from 'fumadocs-ui/layouts/docs';
import {baseOptions} from '@/lib/layout.shared';
import {source} from '@/lib/source';
export default function Layout({children}: LayoutProps<'/'>) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions()}>
{children}
</DocsLayout>
);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {createFromSource} from 'fumadocs-core/search/server';
import {source} from '@/lib/source';
export const {GET} = createFromSource(source, {
language: 'english',
});

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
:root {
--saturation-factor: 1;
/* Dark theme - use brand-primary-light */
--color-fd-primary: hsl(242, calc(100% * var(--saturation-factor)), 84%);
}
.light {
/* Light theme - use brand-primary */
--color-fd-primary: hsl(242, calc(70% * var(--saturation-factor)), 55%);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {RootProvider} from 'fumadocs-ui/provider/next';
import './global.css';
import type {Metadata, Viewport} from 'next';
import {Inter} from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
});
export const metadata: Metadata = {
title: {
template: 'Fluxer API Docs | %s',
default: 'Fluxer API Docs',
},
description: 'Official API documentation for Fluxer',
icons: {
icon: [
{url: 'https://fluxerstatic.com/web/favicon.ico'},
{url: 'https://fluxerstatic.com/web/favicon-16x16.png', sizes: '16x16', type: 'image/png'},
{url: 'https://fluxerstatic.com/web/favicon-32x32.png', sizes: '32x32', type: 'image/png'},
],
apple: {url: 'https://fluxerstatic.com/web/apple-touch-icon.png', sizes: '180x180'},
},
};
export const viewport: Viewport = {
themeColor: '#4641D9',
};
export default function Layout({children}: LayoutProps<'/'>) {
return (
<html lang="en" className={inter.className} suppressHydrationWarning>
<body className="flex min-h-screen flex-col">
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {getLLMText, source} from '@/lib/source';
export const revalidate = false;
export async function GET() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);
return new Response(scanned.join('\n\n'));
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {generate as DefaultImage} from 'fumadocs-ui/og';
import {notFound} from 'next/navigation';
import {ImageResponse} from 'next/og';
import {getPageImage, source} from '@/lib/source';
export const revalidate = false;
export async function GET(_req: Request, {params}: RouteContext<'/og/docs/[...slug]'>) {
const {slug} = await params;
const page = source.getPage(slug.slice(0, -1));
if (!page) notFound();
return new ImageResponse(<DefaultImage title={page.data.title} description={page.data.description} site="My App" />, {
width: 1200,
height: 630,
});
}
export function generateStaticParams() {
return source.getPages().map((page) => ({
lang: page.locale,
slug: getPageImage(page).segments,
}));
}

View File

@@ -0,0 +1,386 @@
---
title: API Reference
description: An overview of how the Fluxer API works and how to use it
---
Fluxer exposes an HTTP API for most read/write operations. Use it to build bots, automations, and integrations that talk to Fluxer from your code. For real-time events, connect to the Fluxer Gateway over WebSocket.
If you've used the [Discord API](https://discord.dev), Fluxer should feel familiar. Most Discord libraries adapt with only small tweaks (for example, changing the base URL).
## Base URL
All HTTP API requests go through the same base URL:
```text
https://api.fluxer.app
```
## API Versioning
Fluxer uses versioned API endpoints. The current version is `v1`, and all documented endpoints are prefixed with `/v1/`.
```text
GET /v1/...
```
If you omit the version prefix, you'll hit the latest stable version. That can change over time, so pin a version like `/v1/` for anything you don't want to break unexpectedly.
## Error Responses
### Generic Error Response
When a request fails, the API returns an HTTP status code plus a JSON body with error details:
```json
{
"code": "UNKNOWN_USER",
"message": "Unknown User"
}
```
`code` is stable and machine-readable. `message` is for humans and may change, so don't rely on it for program logic.
### Form Validation Errors
For endpoints that accept form data, failed validation returns `400 Bad Request` and a list of field-level errors:
```json
{
"code": "INVALID_FORM_BODY",
"message": "Input Validation Error",
"errors": [
{"path": "username", "message": "Username must be at least 3 characters long."},
{"path": "email", "message": "Email must be a valid email address."}
]
}
```
For nested structures, `path` uses dot and index notation to point at the field that failed:
```json
{
"code": "INVALID_FORM_BODY",
"message": "Input Validation Error",
"errors": [
{"path": "profile.address.street", "message": "Street is required."},
{"path": "items.0.quantity", "message": "Quantity must be a positive integer."}
]
}
```
Treat `code` as the stable part of the error and use `errors[*].path` to locate invalid inputs.
## Authentication
Most API requests require authentication. Send your bot token in the `Authorization` header:
```text
Authorization: Bot YOUR_BOT_TOKEN
```
Replace `YOUR_BOT_TOKEN` with the token from **User Settings > Applications** in the Fluxer web/desktop app.
Keep bot tokens secret. Don't commit them to source control or ship them in client-side code.
## Encryption
All HTTP and WebSocket connections to Fluxer must use TLS (`https://` and `wss://`). The minimum supported version is TLS 1.2.
Plain `http://` and `ws://` endpoints are not available.
## Snowflakes
Fluxer uses [Twitter Snowflakes](https://github.com/twitter-archive/snowflake/tree/snowflake-2010) as IDs for users, messages, channels, and other entities.
Within a single entity type (for example, messages), Snowflakes are unique. Different entity types may share the same numeric Snowflake, so always track which entity an ID refers to.
Snowflakes are 64-bit unsigned integers that encode a timestamp plus some extra bits. In JSON, they're always sent as strings to avoid precision issues. Treat them as opaque strings unless you need to decode them.
| Component | Bits | Description |
| --------------- | ---- | ------------------------------------------------------------------------------- |
| Timestamp | 42 | Milliseconds since the Fluxer epoch (`1420070400000`, the first second of 2015) |
| Worker ID | 5 | ID of the worker that generated the Snowflake |
| Process ID | 5 | ID of the process that generated the Snowflake |
| Sequence Number | 12 | Counter for IDs generated in the same millisecond |
If you decode the timestamp part, you can tell when a resource was created.
```ts
function snowflakeToTimestamp(snowflake: string): number {
const fluxerEpoch = 1420070400000n;
const timestamp = (BigInt(snowflake) >> 22n) + fluxerEpoch;
return Number(timestamp);
}
```
### Pagination with Snowflakes
Many API endpoints return lists. These endpoints accept `before`, `after`, and `limit` query parameters. Check the individual endpoint docs to see which ones support pagination.
Because Snowflake IDs encode timestamps, you can generate Snowflakes from timestamps to paginate by time:
```ts
function timestampToSnowflake(timestamp: number): string {
const fluxerEpoch = 1420070400000n;
const snowflake = (BigInt(timestamp) - fluxerEpoch) << 22n;
return snowflake.toString();
}
const currentTimeMs = Date.now();
const currentSnowflake = timestampToSnowflake(currentTimeMs);
```
To specify the beginning of time, use the Snowflake `0`.
## ISO 8601 Timestamps
Timestamps in Fluxer API responses use the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) format in UTC. Parse them with standard libraries in your language of choice.
## Nullable & Optional Fields
To stay close to Discord's API docs, the Fluxer API uses this convention for nullable and optional fields in tables:
| Field Name | Type | Description |
| -------------------------- | ------- | -------------------------------------------------------------------------- |
| `optional_field?` | string | An optional field that may be missing in the response |
| `nullable_field` | ?string | A nullable field that may be `null` in the response, but is always present |
| `optional_nullable_field?` | ?string | An optional field that may be missing or `null` in the response |
## Backwards Compatibility
New response fields (documented or not) are backwards-compatible. Your client should ignore fields it doesn't recognize.
Breaking changes will be announced in advance, with a migration period where old and new versions both work.
## HTTP API
### User Agent
When making HTTP requests, set a custom `User-Agent` header that identifies your application. Example:
```text
User-Agent: MyFluxerBot ($url, $version)
```
### Content Type
Set the `Content-Type` header to `application/json`, `application/x-www-form-urlencoded`, or `multipart/form-data` as appropriate for your request body.
### Rate Limits
Rate limits are reported via HTTP headers. If you exceed a limit, you'll receive `429 Too Many Requests`. For details, see the [Rate Limits](./rate-limits) documentation.
## Gateway API (WebSocket)
Fluxer's Gateway API lets you maintain a long-lived WebSocket connection for real-time events. For how to connect and interact, see the [Gateway API](./gateway) documentation.
## Message Formatting
Fluxer supports rich message formatting using Markdown-style syntax. Compatibility with most Discord-flavored Markdown is intentional so you can port bots and integrations with minimal changes.
### Format Types
In addition to standard formatting such as bold, italics, underline, strikethrough, inline code, code blocks, blockquotes, headings, lists, and links, Fluxer supports the following formatting options:
| Type | Syntax | Example |
| ------------------------- | --------------------- | ---------------------------------------- |
| Spoiler | `\|\|text\|\|` | `\|\|spoiler\|\|` (hidden until clicked) |
| Subtext (small gray text) | `-# your text` | `-# extra context / footnote` |
| Email link | `<hello@example.com>` | `<hello@example.com>` |
| Phone link | `<+1234567890>` | `<+1234567890>` |
| Mention user | `<@user_id>` | `<@123456789012345678>` |
| Mention channel | `<#channel_id>` | `<#123456789012345678>` |
| Mention role | `<@&role_id>` | `<@&123456789012345678>` |
| Standard emoji | Unicode emoji | 😄 |
| Custom emoji | `<:name:emoji_id>` | `<:custom_emoji:123456789012345678>` |
| Custom emoji (animated) | `<a:name:emoji_id>` | `<a:animated_emoji:123456789012345678>` |
| Unix timestamp | `<t:timestamp>` | `<t:1445444940>` |
| Unix timestamp (styled) | `<t:timestamp:style>` | `<t:1445444940:R>` |
### Timestamp Styles
When using Unix timestamps, you can specify a style character to control how the timestamp is displayed:
| Style | Description | Example Output |
| ----- | ----------------------- | ------------------------------------ |
| `t` | Short time | 16:29 |
| `T` | Medium time | 16:29:00 |
| `d` | Short date | 10/21/2015 |
| `D` | Long date | October 21, 2015 |
| `f`\* | Long date, short time | October 21, 2015 at 16:29 |
| `F` | Full date, short time | Wednesday, October 21, 2015 at 16:29 |
| `s` | Short date, short time | 10/21/2015, 16:29 |
| `S` | Short date, medium time | 10/21/2015, 16:29:00 |
| `R` | Relative time | 2 hours ago |
\* Default if no style is provided.
## User Content CDN
### Base URL
```text
https://fluxerusercontent.com
```
All user-uploaded media content is served through the User Content CDN at the above base URL.
### CDN Endpoints
| Resource Type | Path Template | Supported Formats |
| ------------------- | ---------------------------------------------------------------- | -------------------- |
| User Avatar | `/avatars/{user_id}/{avatar_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Guild Icon | `/icons/{guild_id}/{icon_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Guild Banner | `/banners/{guild_id}/{banner_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Guild Splash | `/splashes/{guild_id}/{splash_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Guild Embed Splash | `/embed-splashes/{guild_id}/{embed_splash_hash}.{ext}` | PNG, JPEG, WebP |
| Custom Emoji | `/emojis/{emoji_id}.{ext}` | PNG, JPEG, WebP, GIF |
| Sticker | `/stickers/{sticker_id}.{ext}` | WebP, GIF |
| Guild Member Avatar | `/guilds/{guild_id}/users/{user_id}/avatars/{avatar_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Guild Member Banner | `/guilds/{guild_id}/users/{user_id}/banners/{banner_hash}.{ext}` | PNG, JPEG, WebP, GIF |
| Attachment | `/attachments/{channel_id}/{attachment_id}/{filename}` | Various |
All CDN endpoints support query parameters for dynamic image transformation and optimization.
### Query Parameters
CDN endpoints support the following query parameters for transforming and optimizing media on the fly:
#### Avatar, Icon, Banner, Splash, and Emoji Endpoints
These endpoints support the `ImageQuerySchema`:
| Parameter | Type | Default | Description |
| ---------- | ------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `size` | integer | `128` | The desired width in pixels. Must be one of: 16, 20, 22, 24, 28, 32, 40, 44, 48, 56, 60, 64, 80, 96, 100, 128, 160, 240, 256, 300, 320, 480, 512, 600, 640, 1024, 1280, 1536, 2048, 3072, 4096. The image will be resized proportionally. |
| `quality` | string | `high` | Image quality preset. Options: `high` (80% quality), `low` (20% quality), `lossless` (100% quality). |
| `animated` | boolean | `false` | Whether to preserve animation for WebP images. **Note:** GIF images are always animated regardless of this parameter. |
#### Attachment Endpoints
Attachment endpoints support more flexible transformations via the `ExternalQuerySchema`:
| Parameter | Type | Default | Description |
| ---------- | ------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `width` | integer | - | The desired width in pixels (1-4096). If only width is specified, height is calculated to maintain aspect ratio. |
| `height` | integer | - | The desired height in pixels (1-4096). If only height is specified, width is calculated to maintain aspect ratio. |
| `format` | string | - | Convert the image to the specified format. Options: `png`, `jpg`, `jpeg`, `webp`, `gif`. |
| `quality` | string | `lossless` | Image quality preset. Options: `high` (80% quality), `low` (20% quality), `lossless` (100% quality). |
| `animated` | boolean | `false` | Whether to preserve animation for GIF and WebP images. Must be set to `true` to preserve animation in attachments. |
<Callout>
For attachment endpoints, if no transformations are specified (no `width`, `height`, `format`, or `quality` other than `lossless`), the original file is served directly.
</Callout>
### Examples
```text
# Get a 256px user avatar in high quality
https://fluxerusercontent.com/avatars/123456789012345678/a_abc123.webp?size=256&quality=high
# Get an animated guild icon
https://fluxerusercontent.com/icons/123456789012345678/a_def456.gif?size=512&animated=true
# Get an attachment resized to 800x600 in WebP format
https://fluxerusercontent.com/attachments/123/456/image.png?width=800&height=600&format=webp
# Stream a video attachment without transformation
https://fluxerusercontent.com/attachments/123/456/video.mp4
```
## Static Assets CDN
### Base URL
```text
https://fluxerstatic.com
```
Default avatars can be accessed through the Static Assets CDN at the above base URL.
### CDN Endpoints
| Resource Type | Path Template | Supported Formats |
| -------------- | ------------------------ | ----------------- |
| Default Avatar | `/avatars/{index}.png`\* | PNG |
\* `{index}` is a number from `0` to `5`, representing the six default avatar options. Use `index = user_id % 6` to select a default avatar based on the user's ID.
```ts
function getDefaultAvatarUrl(userId: string): string {
const index = Number(BigInt(userId) % 6n);
return `https://fluxerstatic.com/avatars/${index}.png`;
}
```
## Image Data
Image data is a data URI scheme supporting PNG, JPEG, WebP, and GIF formats. It consists of a prefix indicating the media type and encoding, followed by the base64-encoded image data.
```text
data:image/{format};base64,{base64_data}
```
Replace `{format}` with the actual image format (for example, `png`, `jpeg`, `webp`, `gif`) and `{base64_data}` with the base64-encoded string of the image.
## Uploading Files
<Callout>
The file upload size limit applies to each file in a request. The default limit is **25 MiB** for free users and **500
MiB** for premium users. At most **10 files** can be uploaded in a single message.
</Callout>
### Multipart Form Data Upload
Upload files directly with your message using `multipart/form-data`:
```javascript
const form = new FormData();
form.append(
'payload_json',
JSON.stringify({
content: 'Check out these files!',
attachments: [
{id: 0, filename: 'cat.png', description: 'A cute cat'},
{id: 1, filename: 'dog.jpg', description: 'A good dog'},
],
}),
);
form.append('files[0]', catImageBlob, 'cat.png');
form.append('files[1]', dogImageBlob, 'dog.jpg');
const response = await fetch('https://api.fluxer.app/v1/channels/123456789012345678/messages', {
method: 'POST',
headers: {
Authorization: 'Bot YOUR_BOT_TOKEN',
},
body: form,
});
```
### Editing Messages with Attachments
Existing attachments must be specified when `PATCH`ing messages with new attachments. Any attachments not specified will be removed and replaced with the specified list. If you don't specify `attachments` at all, the existing attachments will remain unchanged.
### Using Attachments in Embeds
Use the special `attachment://{filename}` URL format to reference uploaded files within embed objects.
<Callout type="warning">
Only PNG, JPEG, WebP, and GIF image formats may be used at this time. Other file types are not supported.
</Callout>
```json
{
"embeds": [
{
"title": "Look at this image",
"image": {
"url": "attachment://cat.png"
}
}
]
}
```

View File

@@ -0,0 +1,6 @@
---
title: Gateway Events
description: Gateway Events
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Gateway
description: Gateway
---
TBD

View File

@@ -0,0 +1,5 @@
{
"title": "Events",
"pages": ["overview", "gateway", "gateway-events"],
"defaultOpen": true
}

View File

@@ -0,0 +1,6 @@
---
title: Overview
description: Events Overview
---
TBD

View File

@@ -0,0 +1,37 @@
---
title: Introduction
description: Welcome to the Fluxer API documentation
---
Learn how to talk to Fluxer from code: build bots, wire up automations, and plug Fluxer into the rest of your stack. Beep boop!
We only document the public APIs meant for you to build on. Some endpoints are used internally by the Fluxer app and may change without notice, so they're not covered here. If you're curious, the whole project is open source — you can always dig through the [code on GitHub](https://github.com/fluxerapp/fluxer) and experiment. Just remember that internal APIs may break at any time, and we don't officially support them.
## Getting Started
1. Create or log into your Fluxer account [via the web app](https://web.fluxer.app/channels/@me) or desktop app.
2. Click the cogwheel in the bottom-left to open **User Settings** → **Applications**.
3. Create your first application and copy the bot token.
Once you've got a bot token, you're ready to hit the Fluxer API. The [Quickstart guide](./quickstart) walks you through building a small bot end-to-end.
If you've used the [Discord API](https://discord.dev), you'll feel at home — Fluxer's API surface is intentionally similar (but not identical). For a quick primer on how Fluxer's API is structured, check out the [API Reference](./api-reference).
## Need Help?
Stuck on something or just want to bounce ideas around?
- Join the [Fluxer Developers community](https://fluxer.gg/fluxer-developers)
- Open a thread in our [GitHub Discussions](https://github.com/fluxerapp/fluxer/discussions)
- Or email us at [developers@fluxer.app](mailto:developers@fluxer.app)
The community is usually the quickest way to get eyes on a question, especially for code examples and debugging.
## Stay Updated
For release notes, new features, and occasional behind-the-scenes updates:
- Follow us on [Bluesky](https://bsky.app/profile/fluxer.app) (@fluxer.app)
- Check the #announcements channel in the [Fluxer Developers community](https://fluxer.gg/fluxer-developers)
Happy coding!

View File

@@ -0,0 +1,14 @@
{
"title": "Documentation",
"pages": [
"index",
"api-reference",
"quickstart",
"---Events",
"events",
"---Resources",
"resources",
"---Topics",
"topics"
]
}

View File

@@ -0,0 +1,188 @@
---
title: Quickstart
description: Build a small Fluxer bot with JavaScript using discord.js core
---
Fluxer's HTTP and Gateway APIs are Discord-compatible in many places. You can use the low-level `discord.js` core packages as your REST + WebSocket clients.
This quickstart:
- gets you to “ping → pong”
- uses only the `discord.js` core packages that map closely to Fluxer
- avoids framework-style abstractions until Fluxer SDKs exist for your language
<Callout type="info" title="Packages used">
- `@discordjs/core`
- `@discordjs/rest`
- `@discordjs/ws`
We're not using the full `discord.js` library. Treat these as typed REST + WebSocket clients that speak the Discord Gateway protocol.
</Callout>
<Callout type="warning" title="Compatibility">
Fluxer is not Discord.
- Discord-only features/events/helpers in the wider `discord.js` ecosystem may not work.
- Fluxer may ship payloads/features these packages don't support.
- For larger or long-lived bots, expect Fluxer-specific code (or use a Fluxer SDK when available).
Use this as a quickstart, not a production architecture.
</Callout>
You will:
- set up a Node.js project
- create a Fluxer application and bot token
- build a minimal “ping → pong” bot
---
## Prerequisites
- [Node.js](https://nodejs.org/en/) (LTS)
- A Fluxer account (https://web.fluxer.app/register)
- A text editor/IDE (for example, [VS Code](https://code.visualstudio.com/))
- Basic JavaScript/Node.js familiarity
<Callout type="warning" title="Keep your bot token secret">
Your bot token controls your bot. Don't commit it to Git. Use environment variables or a
secrets manager.
</Callout>
---
## Step 1: Set up your project
1. Create a directory:
```bash
mkdir fluxer-quickstart
cd fluxer-quickstart
```
2. Create `package.json`:
```bash
echo '{
"name": "fluxer-quickstart",
"version": "1.0.0",
"type": "module",
"main": "bot.js"
}' > package.json
```
3. Install dependencies:
```bash
npm install @discordjs/core @discordjs/rest @discordjs/ws
```
---
## Step 2: Create a Fluxer application and bot
1. Open the Fluxer app and sign in.
2. Go to **User Settings → Applications**.
3. Create an application.
4. On the application page:
- Copy the bot token.
- In **OAuth2 URL Builder**, select **Bot**.
- (Optional) Pre-grant permissions.
- Copy the **Authorize URL**.
![OAuth2 URL Builder with the Bot scope selected](https://fluxerstatic.com/docs/oauth2-url-builder.avif)
5. Open the Authorize URL, pick a community, and click **Authorize**.
---
## Step 3: Set your bot token
Set `FLUXER_BOT_TOKEN`.
macOS/Linux:
```bash
export FLUXER_BOT_TOKEN="your_bot_token_here"
```
Windows (Command Prompt):
```cmd
set FLUXER_BOT_TOKEN="your_bot_token_here"
```
Windows (PowerShell):
```powershell
$Env:FLUXER_BOT_TOKEN = "your_bot_token_here"
```
---
## Step 4: Write the bot
Create `bot.js`:
```javascript
import { Client, GatewayDispatchEvents } from "@discordjs/core";
import { REST } from "@discordjs/rest";
import { WebSocketManager } from "@discordjs/ws";
const token = process.env.FLUXER_BOT_TOKEN;
if (!token) {
console.error("Missing FLUXER_BOT_TOKEN environment variable.");
process.exit(1);
}
const rest = new REST({
api: "https://api.fluxer.app",
version: "1",
}).setToken(token);
const gateway = new WebSocketManager({
token,
intents: 0, // Fluxer has no intents yet
rest,
version: "1",
});
export const client = new Client({ rest, gateway });
client.on(GatewayDispatchEvents.MessageCreate, async ({ api, data }) => {
if (data.content === "ping") {
await api.channels.createMessage(data.channel_id, { content: "pong" });
}
});
client.on(GatewayDispatchEvents.Ready, ({ data }) => {
console.log(`Logged in as ${data.user.username}#${data.user.discriminator}`);
});
gateway.connect();
```
---
## Step 5: Run the bot
```bash
node bot.js
```
Expected output:
```text
Logged in as MyFluxerBot#0000
```
Send `ping` in a channel the bot can read/write. It should reply `pong`.
If it doesn't:
- Ensure `FLUXER_BOT_TOKEN` is set in the same shell.
- Confirm the bot is authorized into the correct community.
- Confirm the bot can access the test channel.
---
## Next steps
- Read the [Fluxer API reference](./api-reference)
- Review [Gateway Events](./events/gateway-events.mdx)
- Add more handlers
- Use Fluxer HTTP calls for features not covered by `discord.js` core
- If you're up for it: build a Fluxer SDK for your language!

View File

@@ -0,0 +1,6 @@
---
title: Audit Log
description: Audit Log
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Channel
description: Channel
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Emoji
description: Emoji
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Guild
description: Guild
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Invite
description: Invite
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Message
description: Message
---
TBD

View File

@@ -0,0 +1,5 @@
{
"title": "Resources",
"pages": ["audit-log", "channel", "emoji", "guild", "invite", "message", "sticker", "user", "voice", "webhook"],
"defaultOpen": true
}

View File

@@ -0,0 +1,6 @@
---
title: Sticker
description: Sticker
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: User
description: User
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Voice
description: Voice
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Webhook
description: Webhook
---
TBD

View File

@@ -0,0 +1,17 @@
---
title: Components
description: Components
---
## Code Block
```js
console.log('Hello World');
```
## Cards
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>

View File

@@ -0,0 +1,5 @@
{
"title": "Topics",
"pages": ["opcodes-status-codes", "permissions", "rate-limits", "voice"],
"defaultOpen": true
}

View File

@@ -0,0 +1,6 @@
---
title: Opcodes & Status Codes
description: Opcodes & Status Codes
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Permissions
description: Permissions
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Rate Limits
description: Rate Limits
---
TBD

View File

@@ -0,0 +1,6 @@
---
title: Voice
description: Voice
---
TBD

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {BaseLayoutProps} from 'fumadocs-ui/layouts/shared';
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'Fluxer API Docs',
},
};
}

45
fluxer_docs/lib/source.ts Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {docs} from 'fumadocs-mdx:collections/server';
import {type InferPageType, loader} from 'fumadocs-core/source';
import {lucideIconsPlugin} from 'fumadocs-core/source/lucide-icons';
export const source = loader({
baseUrl: '/',
source: docs.toFumadocsSource(),
plugins: [lucideIconsPlugin()],
});
export function getPageImage(page: InferPageType<typeof source>) {
const segments = [...page.slugs, 'image.png'];
return {
segments,
url: `/og/docs/${segments.join('/')}`,
};
}
export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await page.data.getText('processed');
return `# ${page.data.title}
${processed}`;
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type {MDXComponents} from 'mdx/types';
import type {ComponentProps} from 'react';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
...components,
// biome-ignore lint/a11y/useAltText: alt is passed through props from MDX content
// biome-ignore lint/performance/noImgElement: using native img for external image linking
img: (props: ComponentProps<'img'>) => <img loading={props.loading ?? 'lazy'} {...props} />,
};
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {createMDX} from 'fumadocs-mdx/next';
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
reactStrictMode: true,
output: 'standalone',
// Disable Next.js image optimization so external image URLs are served directly.
images: {
unoptimized: true,
},
};
export default withMDX(config);

38
fluxer_docs/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "fluxer_docs",
"version": "0.0.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"start": "next start",
"types:check": "fumadocs-mdx && tsc --noEmit",
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"fumadocs-core": "16.2.5",
"fumadocs-mdx": "14.1.0",
"fumadocs-ui": "16.2.5",
"lucide-react": "0.561.0",
"next": "16.0.10",
"react": "19.2.3",
"react-dom": "19.2.3"
},
"devDependencies": {
"@tailwindcss/postcss": "4.1.18",
"@types/mdx": "2.0.13",
"@types/node": "25.0.1",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"postcss": "8.5.6",
"tailwindcss": "4.1.18",
"typescript": "5.9.3",
"vite": "7.2.7"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"sharp"
]
}
}

4399
fluxer_docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};

View File

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {defineConfig, defineDocs, frontmatterSchema, metaSchema} from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
schema: frontmatterSchema,
postprocess: {
includeProcessedMarkdown: true,
},
},
meta: {
schema: metaSchema,
},
});
export default defineConfig({
mdxOptions: {},
});

30
fluxer_docs/tsconfig.json Normal file
View File

@@ -0,0 +1,30 @@
{
"compilerOptions": {
"baseUrl": ".",
"target": "ESNext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"paths": {
"@/*": ["./*"],
"fumadocs-mdx:collections/*": [".source/*"]
},
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
"exclude": ["node_modules"]
}