# Flung documentation, in full > Connect the social accounts of the people using your app, then schedule and publish posts to X, LinkedIn, Instagram, Bluesky and more over REST, from TypeScript, or through MCP. This file holds 18 pages of the Flung documentation, one after another, each starting with its own title and the address it is served at. The index of every page, including the optional ones left out here, is https://docs.flung.dev/llms.txt. --- # Quick start > Page: https://docs.flung.dev/quick-start. Index of every page: https://docs.flung.dev/llms.txt Connect a social account and publish your first post through Flung, from your server or from an AI agent. ## TL;DR | Question | Answer | |---|---| | What do I need? | A Flung API key (it starts `flung_sk_`), kept on your server. | | How long does it take? | A few minutes: install, connect one account, post. | | Can an AI agent do this for me? | Yes. Skip to [Connect an AI agent](https://docs.flung.dev/mcp.md), then ask it to "connect my LinkedIn and post that we're live". | ## 1. Install the SDK ```bash npm install @flung.dev/sdk ``` The SDK runs on a server: Node 20 or newer, Cloudflare Workers, Deno or Bun. Keep the key in an environment variable such as `FLUNG_API_KEY`. ## 2. Connect an account Start a connect, send the person to the `authorizationUrl` it returns, and wait until they approve on the platform. **curl** ```bash curl -X POST 'https://api.flung.dev/v1/connect/linkedin' \ --header "Authorization: Bearer $FLUNG_API_KEY" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "redirectUrl": "https://app.example.com/settings/social", "handle": "alice.bsky.social" }' ``` **TypeScript** ```ts import { Flung } from '@flung.dev/sdk' const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! }) const result = await flung.accounts.connect({ platform: 'linkedin', redirectUrl: 'https://app.example.com/settings/social', handle: 'alice.bsky.social', }) ``` **fetch** ```js const response = await fetch('https://api.flung.dev/v1/connect/linkedin', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ redirectUrl: 'https://app.example.com/settings/social', handle: 'alice.bsky.social', }), }) if (!response.ok) throw new Error((await response.json()).error.message) const result = await response.json() ``` The SDK waits for you: ```ts const session = await flung.accounts.connect({ platform: 'linkedin' }) console.warn(`Open ${session.authorizationUrl}`) const done = await flung.waitForConnection(session.id) ``` To let your own users connect accounts on your website instead, use the [connect panel](https://docs.flung.dev/connect-accounts.md). ## 3. Publish a post List the connected accounts, then post to them. Leave out `publishAt` to publish now, or give a time to schedule it. **curl** ```bash curl -X GET 'https://api.flung.dev/v1/accounts?reconnectBefore=2026-11-30T00%3A00%3A00Z' \ --header "Authorization: Bearer $FLUNG_API_KEY" ``` **TypeScript** ```ts import { Flung } from '@flung.dev/sdk' const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! }) const result = await flung.accounts.list({ reconnectBefore: '2026-11-30T00:00:00Z', }) ``` **fetch** ```js const response = await fetch('https://api.flung.dev/v1/accounts?reconnectBefore=2026-11-30T00%3A00%3A00Z', { method: 'GET', headers: { 'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`, }, }) if (!response.ok) throw new Error((await response.json()).error.message) const result = await response.json() ``` **curl** ```bash curl -X POST 'https://api.flung.dev/v1/posts' \ --header "Authorization: Bearer $FLUNG_API_KEY" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "content": "Launching today.", "accountIds": [ "acct_aaaaaaaaaaaaaaaaaaaaaaaa" ], "platformOverrides": { "x": { "content": "Launching today. #launch" } }, "variables": { "product": "Flung" } }' ``` **TypeScript** ```ts import { Flung } from '@flung.dev/sdk' const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! }) const result = await flung.posts.create({ content: 'Launching today.', accountIds: [ 'acct_aaaaaaaaaaaaaaaaaaaaaaaa', ], platformOverrides: { x: { content: 'Launching today. #launch', }, }, variables: { product: 'Flung', }, }) ``` **fetch** ```js const response = await fetch('https://api.flung.dev/v1/posts', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ content: 'Launching today.', accountIds: [ 'acct_aaaaaaaaaaaaaaaaaaaaaaaa', ], platformOverrides: { x: { content: 'Launching today. #launch', }, }, variables: { product: 'Flung', }, }), }) if (!response.ok) throw new Error((await response.json()).error.message) const result = await response.json() ``` The answer has a `target` per account. Read the post again, or listen for webhooks, to see each one published or failed. ## Where next | To | Read | |---|---| | Let your users connect accounts on your site | [Add account connecting to your site](https://docs.flung.dev/connect-accounts.md) | | Attach images and video | [Images and video](https://docs.flung.dev/media.md) | | Be told when posts go out | [Webhooks](https://docs.flung.dev/webhooks.md) | | Give an AI agent the same powers | [Connect an AI agent](https://docs.flung.dev/mcp.md) | | See every endpoint | [API reference](https://docs.flung.dev/reference/api.md) | --- # Authentication > Page: https://docs.flung.dev/authentication. Index of every page: https://docs.flung.dev/llms.txt Every call to the Flung API carries one credential in the `Authorization` header. There are three kinds, for three places a call can come from. ## TL;DR | Credential | Looks like | Use it from | Can do | |---|---|---|---| | API key | `flung_sk_...` | Your server | Everything in its workspace | | OAuth access token | An opaque token | An app or agent a person approved | Everything the person may do in the workspace they chose | | Connect token | `flung_ct_...` | A web page | Only list, connect, reconnect and disconnect accounts, from the origins it was made for | Send any of them as `Authorization: Bearer `. ## API keys An API key belongs to a workspace, not a person. Keep it on a server, in an environment variable, and never in code a browser loads, a repository or a log. A full-access key can create and revoke keys with the [API keys endpoints](https://docs.flung.dev/reference/api.md#api-keys); to rotate one, create the new key, deploy it, then revoke the old one. ## OAuth for apps and agents An app or an AI agent can connect to Flung the way apps connect to Google: a person signs in with a link sent to their email, picks the workspace, and approves. The app gets an access token that lasts an hour and a refresh token that lasts 90 days. Flung's authorization server describes itself at `https://api.flung.dev/.well-known/oauth-authorization-server`, and MCP clients find it by themselves. Access stops at once if the person is removed from the workspace. ## Connect tokens for web pages A page must never hold an API key, so your server makes a connect token for it. The token lasts an hour by default (one minute to one day), works only from the origins you list (https, or `http://localhost` for trying it out), and reaches only the calls a connect panel needs. Give it a `profileId` to keep each of your users to their own accounts, and `platforms` to offer only some. **curl** ```bash curl -X POST 'https://api.flung.dev/v1/connect-tokens' \ --header "Authorization: Bearer $FLUNG_API_KEY" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "origins": [ "https://app.example.com" ], "platforms": [ "x", "linkedin", "bluesky" ] }' ``` **TypeScript** ```ts import { Flung } from '@flung.dev/sdk' const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! }) const result = await flung.connectTokens.create({ origins: [ 'https://app.example.com', ], platforms: [ 'x', 'linkedin', 'bluesky', ], }) ``` **fetch** ```js const response = await fetch('https://api.flung.dev/v1/connect-tokens', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ origins: [ 'https://app.example.com', ], platforms: [ 'x', 'linkedin', 'bluesky', ], }), }) if (!response.ok) throw new Error((await response.json()).error.message) const result = await response.json() ``` Hand the `token` to the page. [Add account connecting to your site](https://docs.flung.dev/connect-accounts.md) shows the whole flow. ## Where things live | Service | Address | |---|---| | REST API | `https://api.flung.dev` | | REST API, preview | `https://api-preview.flung.dev` | | MCP server | `https://mcp.flung.dev` | | Brand files | `https://assets.flung.dev` | | These docs | `https://docs.flung.dev` | --- # Connect an AI agent > Page: https://docs.flung.dev/mcp. Index of every page: https://docs.flung.dev/llms.txt Give an AI agent Flung's tools through its MCP server, so it can connect accounts, schedule posts, retry failures and answer comments for you. ## TL;DR | Question | Answer | |---|---| | Is there anything to install? | No. The server is hosted; your client connects to it over HTTP. | | How does it sign in? | Through your browser: you approve the agent and pick the workspace. Or with an API key, for clients that cannot sign in. | | Can the agent connect an account by itself? | No. It gives you the platform's link; you approve there. | | What can it do? | Every tool is listed in [MCP tools](https://docs.flung.dev/reference/mcp.md). | ## Add the server **Claude Code** ```bash claude mcp add --transport http flung https://mcp.flung.dev/mcp-oauth ``` **Cursor** ```json { "mcpServers": { "flung": { "url": "https://mcp.flung.dev/mcp-oauth" } } } ``` **VS Code** ```json { "servers": { "flung": { "type": "http", "url": "https://mcp.flung.dev/mcp-oauth" } } } ``` **API key** ```bash claude mcp add --transport http flung https://mcp.flung.dev/mcp --header "X-API-Key: $FLUNG_API_KEY" ``` In Claude Desktop and on claude.ai, add a custom connector with the address from the Claude Code line. ## Try it Ask your agent: - "Which of my social accounts need reconnecting?" - "Connect my LinkedIn." - "Post 'We're live' to X and Bluesky tomorrow at 9am my time." More are in [Prompts and recipes](https://docs.flung.dev/agents/prompts.md). To make every project's agent use Flung the same way, paste the [rules file](https://docs.flung.dev/agents/rules.md) into it. --- # Add account connecting to your site > Page: https://docs.flung.dev/connect-accounts. Index of every page: https://docs.flung.dev/llms.txt Put a panel on your own page where your users connect, reconnect and disconnect their social accounts, without your API key ever reaching the browser. ## TL;DR | Question | Answer | |---|---| | What are the parts? | A route on your server that makes a connect token, and `` on the page, which uses it. | | What does a person see? | The platforms you offer and their connected accounts, with Connect, Reconnect and Disconnect. Connecting opens the platform's own sign-in in a small window. | | Which frameworks? | Any. Next.js, React with Vite and a plain page are shown below, each from a working example. | | Can each of my users have their own accounts? | Yes: make each token with that user's `profileId`. | ## Install ```bash npm install @flung.dev/sdk @flung.dev/connect ``` ## Next.js The server keeps the key, in `.env.local`: **.env.local** ```bash # A full-access Flung API key (flung_sk_...). Server only: never prefix it with NEXT_PUBLIC_. FLUNG_API_KEY= # Optional. The API: https://api.flung.dev (the default) or https://api-preview.flung.dev. FLUNG_BASE_URL= # The origin this app is served from, which connect tokens are made for. http://localhost:3000 when left out. APP_ORIGIN= ``` A route makes a connect token for the page. Put your own sign-in check at its top: **app/api/connect-token/route.ts** ```ts import { Flung, FlungError } from '@flung.dev/sdk' /** * `POST /api/connect-token`: a short-lived connect token for this app's own page. The API key stays here on the server; * the token it makes can only list, connect, reconnect and disconnect accounts, from `APP_ORIGIN`, for fifteen minutes. * * In a real app, check who is signed in first, and pass their profile as `profileId` so each person sees only theirs. */ export async function POST(): Promise { const apiKey = process.env.FLUNG_API_KEY if (apiKey === undefined || apiKey === '') return Response.json({ error: 'Set FLUNG_API_KEY in .env.local.' }, { status: 500 }) const flung = new Flung({ apiKey, ...(process.env.FLUNG_BASE_URL ? { baseUrl: process.env.FLUNG_BASE_URL } : {}) }) try { const { token, expiresAt } = await flung.connectTokens.create({ origins: [process.env.APP_ORIGIN || 'http://localhost:3000'], expiresIn: 900 }) return Response.json({ token, expiresAt }, { headers: { 'cache-control': 'no-store' } }) } catch (error) { const status = error instanceof FlungError ? (error.status ?? 502) : 502 return Response.json({ error: error instanceof Error ? error.message : 'Could not make a connect token.' }, { status }) } } ``` The page shows the panel, which asks the route for a token, and again when one expires: **app/accounts.tsx** ```tsx 'use client' import { useCallback, useState } from 'react' import { FlungConnect } from '@flung.dev/connect/react' /** Asks this app's server for a connect token; the panel calls it again when a token expires. */ async function connectToken(): Promise { const response = await fetch('/api/connect-token', { method: 'POST' }) const body = (await response.json()) as { token?: string; error?: string } if (!response.ok || body.token === undefined) throw new Error(body.error ?? 'Could not make a connect token.') return body.token } export function Accounts({ baseUrl }: { baseUrl: string | undefined }) { const [events, setEvents] = useState([]) const log = useCallback((line: string) => setEvents((previous) => [line, ...previous].slice(0, 5)), []) return ( <> log(`Connected ${accounts.map((account) => account.username ?? account.id).join(', ')}`)} onDisconnect={({ account }) => log(`Disconnected ${account.username ?? account.id}`)} onError={({ code, message }) => log(`${code}: ${message}`)} /> {events.length > 0 && (
    {events.map((line, index) => (
  • {line}
  • ))}
)} ) } ``` ## React with Vite For trying the panel out, Vite's development server can make the token itself: **vite.config.ts** ```ts import { Flung } from '@flung.dev/sdk' import { defineConfig, loadEnv } from 'vite' import type { Connect, Plugin } from 'vite' /** * `POST /connect-token` on the dev and preview servers: makes a connect token for the page with the key from * `.env.local`. The key has no `VITE_` prefix, so Vite never puts it in the bundle. This is for trying the panel out; a * deployed app makes tokens on its own server, behind its own sign-in. */ function connectTokens(apiKey: string, baseUrl: string | undefined): Plugin { const handler: Connect.NextHandleFunction = (request, response, next) => { if (request.method !== 'POST' || request.url !== '/connect-token') return next() const send = (status: number, body: unknown) => { response.statusCode = status response.setHeader('content-type', 'application/json') response.setHeader('cache-control', 'no-store') response.end(JSON.stringify(body)) } if (apiKey === '') return send(500, { error: 'Set FLUNG_API_KEY in .env.local.' }) const origin = request.headers.origin ?? `http://${request.headers.host ?? 'localhost:5173'}` const flung = new Flung({ apiKey, ...(baseUrl ? { baseUrl } : {}) }) flung.connectTokens.create({ origins: [origin], expiresIn: 900 }).then( ({ token }) => send(200, { token }), (error: unknown) => send(502, { error: error instanceof Error ? error.message : 'Could not make a connect token.' }), ) } return { name: 'flung-connect-tokens', configureServer: (server) => void server.middlewares.use(handler), configurePreviewServer: (server) => void server.middlewares.use(handler) } } export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') return { plugins: [connectTokens(env.FLUNG_API_KEY ?? '', env.FLUNG_BASE_URL || undefined)], define: { 'import.meta.env.FLUNG_BASE_URL': JSON.stringify(env.FLUNG_BASE_URL || '') }, esbuild: { jsx: 'automatic' }, } }) ``` **src/App.tsx** ```tsx import { useCallback, useState } from 'react' import { FlungConnect } from '@flung.dev/connect/react' /** Asks the dev server for a connect token; the panel calls it again when a token expires. */ async function connectToken(): Promise { const response = await fetch('/connect-token', { method: 'POST' }) const body = (await response.json()) as { token?: string; error?: string } if (!response.ok || body.token === undefined) throw new Error(body.error ?? 'Could not make a connect token.') return body.token } const baseUrl = import.meta.env.FLUNG_BASE_URL || undefined export function App() { const [events, setEvents] = useState([]) const log = useCallback((line: string) => setEvents((previous) => [line, ...previous].slice(0, 5)), []) return (

Connect your accounts

We post to the accounts you connect here. You can disconnect them at any time.

log(`Connected ${accounts.map((account) => account.username ?? account.id).join(', ')}`)} onDisconnect={({ account }) => log(`Disconnected ${account.username ?? account.id}`)} onError={({ code, message }) => log(`${code}: ${message}`)} /> {events.length > 0 && (
    {events.map((line, index) => (
  • {line}
  • ))}
)}
) } ``` A deployed app makes tokens on its own server, behind its own sign-in, as the Next.js route does. ## A plain page No framework and nothing to install: a server of a few lines makes the token, and the page loads the panel. **server.mjs** ```js #!/usr/bin/env node /** * The smallest server a connect panel needs, with nothing to install: it keeps the API key, makes a connect token for * the page when the page asks, and serves the page and the built package. * * pnpm build * FLUNG_API_KEY=flung_sk_... node examples/vanilla/server.mjs * * Then open http://localhost:5173. FLUNG_BASE_URL=https://api-preview.flung.dev tries it against preview. */ import { readFile } from 'node:fs/promises' import { createServer } from 'node:http' import { extname, join, normalize } from 'node:path' const PORT = Number(process.env.PORT ?? 5173) const ORIGIN = `http://localhost:${PORT}` const BASE_URL = process.env.FLUNG_BASE_URL ?? 'https://api.flung.dev' const API_KEY = process.env.FLUNG_API_KEY if (API_KEY === undefined) throw new Error('Set FLUNG_API_KEY to a full-access Flung API key.') const here = import.meta.dirname const dist = join(here, '..', '..', 'packages', 'connect', 'dist') const TYPES = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8' } /** In your app, check who is signed in here first, and pass their profile as `profileId`. */ async function connectToken() { const response = await fetch(`${BASE_URL}/v1/connect-tokens`, { method: 'POST', headers: { authorization: `Bearer ${API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ origins: [ORIGIN], expiresIn: 900 }), }) return { status: response.status, body: await response.text() } } createServer(async (request, response) => { const path = new URL(request.url ?? '/', ORIGIN).pathname if (request.method === 'POST' && path === '/connect-token') { const made = await connectToken() response.writeHead(made.status, { 'content-type': 'application/json' }).end(made.body) return } const file = path === '/' ? join(here, 'index.html') : path.startsWith('/connect/') ? normalize(join(dist, path.slice('/connect/'.length))) : null if (file === null || (file !== join(here, 'index.html') && !file.startsWith(dist))) { response.writeHead(404).end() return } try { const page = file.endsWith('index.html') ? (await readFile(file, 'utf8')).replace('__FLUNG_BASE_URL__', BASE_URL) : await readFile(file) response.writeHead(200, { 'content-type': TYPES[extname(file)] ?? 'application/octet-stream' }).end(page) } catch { response.writeHead(404).end() } }).listen(PORT, () => process.stdout.write(`Open ${ORIGIN}\n`)) ``` **index.html** ```html Connect your accounts

Connect your accounts

Flung posts to the accounts you connect here. You can disconnect them at any time.


    
  

```

## How it behaves

- The panel offers only the platforms this Flung can connect, narrowed by its `platforms` attribute and the token's own list.
- Connecting opens the platform's sign-in in a small window. When a browser blocks the window, the panel shows a link instead.
- Mastodon asks for the person's server first; Bluesky takes a handle when they have one.
- Disconnecting asks to confirm. The account then leaves the panel.
- With `mode="redirect"` the whole page goes to the platform and back, for pages that cannot open windows.

Every attribute, event, style hook and word is in the [connect panel reference](https://docs.flung.dev/reference/connect.md).

---

# Publish and schedule posts

> Page: https://docs.flung.dev/posting. Index of every page: https://docs.flung.dev/llms.txt

Post to one account or many at once, now or at a set time, and see what happened on each.

## TL;DR

| Question | Answer |
|---|---|
| How do I post? | `POST /v1/posts` with the text and the `accountIds`. |
| How do I schedule? | Add `publishAt`, an ISO 8601 time. Leave it out to publish now. |
| Can the text differ per platform? | Yes: `platformOverrides` sets a platform's own text and options. |
| What if a platform fails? | The post is `partially_published` or `failed`, with a reason per account. Retrying publishes only where it failed. |
| Can I change a scheduled post? | Yes, until it starts publishing; or cancel it. |

## Publish

**curl**

```bash
curl -X POST 'https://api.flung.dev/v1/posts' \
  --header "Authorization: Bearer $FLUNG_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
  "content": "Launching today.",
  "accountIds": [
    "acct_aaaaaaaaaaaaaaaaaaaaaaaa"
  ],
  "platformOverrides": {
    "x": {
      "content": "Launching today. #launch"
    }
  },
  "variables": {
    "product": "Flung"
  }
}'
```

**TypeScript**

```ts
import { Flung } from '@flung.dev/sdk'

const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! })
const result = await flung.posts.create({
  content: 'Launching today.',
  accountIds: [
    'acct_aaaaaaaaaaaaaaaaaaaaaaaa',
  ],
  platformOverrides: {
    x: {
      content: 'Launching today. #launch',
    },
  },
  variables: {
    product: 'Flung',
  },
})
```

**fetch**

```js
const response = await fetch('https://api.flung.dev/v1/posts', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    content: 'Launching today.',
    accountIds: [
      'acct_aaaaaaaaaaaaaaaaaaaaaaaa',
    ],
    platformOverrides: {
      x: {
        content: 'Launching today. #launch',
      },
    },
    variables: {
      product: 'Flung',
    },
  }),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const result = await response.json()
```

Every POST carries an `Idempotency-Key`. Send the same key when you retry, and you get the first answer back instead of a second post. The SDK does this by itself.

## What happens next

Each account the post goes to is a target. A target is `pending` until its time, then `publishing`, then `published` with the platform's link, or `failed` with the reason. The post's own status sums them up: `published`, `partially_published` or `failed`.

Read the post again with [Get a post](https://docs.flung.dev/reference/api/get-v1-posts-id.md), or listen for `post.published` and `post.failed` with [webhooks](https://docs.flung.dev/webhooks.md).

## Platforms

Flung posts to these platforms:

`x`, `instagram`, `facebook`, `threads`, `linkedin`, `tiktok`, `youtube`, `pinterest`, `bluesky`, `mastodon`, `discord`, `farcaster`

Each platform has its own limits on text, images and video; [List platforms](https://docs.flung.dev/reference/api/get-v1-platforms.md) gives them, and a post a platform cannot carry is refused when you create it, not when it fails to publish.

## When to post

[Suggest posting times](https://docs.flung.dev/reference/api/get-v1-posting-times.md) gives the next times a platform's audience is usually most active, ready to pass as `publishAt`.

## Templates, variables and feeds

Save a post you send often as a template and schedule it again with one call. `{{name}}` placeholders in any post are filled from the workspace's variables, and an RSS or Atom feed can post each new item for you. See the Templates, Variables and Feeds sections of the [API reference](https://docs.flung.dev/reference/api.md).

---

# Images and video

> Page: https://docs.flung.dev/media. Index of every page: https://docs.flung.dev/llms.txt

Attach images or a video to a post: upload the file, or let Flung fetch it from a link, then pass its id in `mediaIds`.

## TL;DR

| Question | Answer |
|---|---|
| Which files? | JPEG, PNG and WebP up to 20 MB, GIF up to 15 MB, and MP4 and QuickTime up to 1 GB. Flung reads the real type from the file itself. |
| How do I upload? | Reserve the file, send its bytes, and use it once it is `ready`. Files over 100 MB go in parts. The SDK does all of it in one call. |
| Can Flung fetch a file from a link? | Yes: import it from a public https address. |
| Does a video get a thumbnail? | Yes, a frame one second in, for showing beside the file. Posts never wait for it. |

## Upload with the SDK

```ts
const image = await flung.uploadMedia(await readFile('launch.png'), { contentType: 'image/png', filename: 'launch.png' })
await flung.posts.create({ content: 'Launching today.', accountIds, mediaIds: [image.id] })
```

## Import from a link

**curl**

```bash
curl -X POST 'https://api.flung.dev/v1/media/import' \
  --header "Authorization: Bearer $FLUNG_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
  "url": "https://example.com/launch.jpg"
}'
```

**TypeScript**

```ts
import { Flung } from '@flung.dev/sdk'

const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! })
const result = await flung.media.import({
  url: 'https://example.com/launch.jpg',
})
```

**fetch**

```js
const response = await fetch('https://api.flung.dev/v1/media/import', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    url: 'https://example.com/launch.jpg',
  }),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const result = await response.json()
```

## What each platform takes

Each platform's own limits apply on top of Flung's, such as Bluesky's 1 MB images or X's four images a post. [List platforms](https://docs.flung.dev/reference/api/get-v1-platforms.md) gives every limit, and a post a platform cannot carry is refused when you create it.

---

# Webhooks

> Page: https://docs.flung.dev/webhooks. Index of every page: https://docs.flung.dev/llms.txt

Have Flung tell your server when posts publish or fail and when accounts need a person, instead of asking again and again.

## TL;DR

| Question | Answer |
|---|---|
| How do I start? | Register an https endpoint and keep the `whsec_` secret it returns. |
| How do I know an event is from Flung? | Verify its signature. Flung signs to the Standard Webhooks spec, so the SDK or any Standard Webhooks library checks it. |
| What if my endpoint is down? | Flung tries again, for a while, with growing gaps. Each event keeps its id, so drop ids you have already handled. |
| Can I rotate the secret? | Yes. Flung signs with both secrets while you switch, and the SDK accepts either. |

## Register an endpoint

**curl**

```bash
curl -X POST 'https://api.flung.dev/v1/webhook-endpoints' \
  --header "Authorization: Bearer $FLUNG_API_KEY" \
  --header 'Content-Type: application/json' \
  --header "Idempotency-Key: $(uuidgen)" \
  --data '{
  "url": "https://example.com/webhooks/flung",
  "events": [
    "post.published",
    "post.failed"
  ]
}'
```

**TypeScript**

```ts
import { Flung } from '@flung.dev/sdk'

const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! })
const result = await flung.webhookEndpoints.create({
  url: 'https://example.com/webhooks/flung',
  events: [
    'post.published',
    'post.failed',
  ],
})
```

**fetch**

```js
const response = await fetch('https://api.flung.dev/v1/webhook-endpoints', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': crypto.randomUUID(),
  },
  body: JSON.stringify({
    url: 'https://example.com/webhooks/flung',
    events: [
      'post.published',
      'post.failed',
    ],
  }),
})
if (!response.ok) throw new Error((await response.json()).error.message)
const result = await response.json()
```

## Verify each event

Read the raw body as text: parsing it and writing it out again changes the bytes and breaks the signature.

```ts
export async function POST(request: Request): Promise {
  const body = await request.text()
  const event = await verifyWebhook({ body, headers: request.headers, secret: process.env.FLUNG_WEBHOOK_SECRET! })
  if (event.type === 'post.failed') console.warn(`Post ${event.data.post.id} failed`)
  return new Response(null, { status: 204 })
}
```

## Events

| Event |
|---|
| `post.published` |
| `post.partially_published` |
| `post.failed` |
| `target.published` |
| `target.failed` |
| `account.connected` |
| `account.disconnected` |
| `account.token_refresh_failed` |
| `account.reconnect_required_soon` |
| `inbox.received` |

Every event has an `id`, a `type`, a `createdAt` time and `data` with the post or the account it is about.

---

# Retries, limits and errors

> Page: https://docs.flung.dev/reliability. Index of every page: https://docs.flung.dev/llms.txt

How Flung keeps a retry from posting twice, how fast you may call it, and what a failure tells you.

## TL;DR

| Question | Answer |
|---|---|
| Is a retry safe? | Yes, with the same `Idempotency-Key`: you get the first answer back. The SDK sends one on every POST. |
| How fast may I call? | 600 requests a minute per API key or OAuth app. Past that the answer is `429` with `Retry-After`. |
| What does a failure look like? | One JSON shape, with a stable `code`. See [Errors](https://docs.flung.dev/reference/api/errors.md). |
| What does the SDK retry by itself? | Network failures, timeouts, 408, 409, 429 and 5xx answers, twice, waiting longer each time and honouring `Retry-After`. |

## Walk every page of a list

Lists return a page at a time with `hasMore`. The SDK walks them for you:

## Accounts that stop working

Platforms end access now and then: a token expires, a password changes, an app review lapses. Flung renews every token it can before it runs out. Accounts it cannot renew turn `needs_reconnect`, and `account.reconnect_required_soon` arrives a week before the ones with a fixed end. A reconnect needs the person, through the [connect panel](https://docs.flung.dev/connect-accounts.md) or a link from [Start reconnecting an account](https://docs.flung.dev/reference/api/post-v1-accounts-id-reconnect.md).

---

# API reference

> Page: https://docs.flung.dev/reference/api. Index of every page: https://docs.flung.dev/llms.txt

Every endpoint of the Flung REST API, grouped by what it does, with a page each that has its parameters, its answers and code samples in curl, TypeScript and fetch.

## TL;DR

| Question | Answer |
|---|---|
| Where is it? | `https://api.flung.dev`. Every path below is relative to it. Preview, for trying changes, is `https://api-preview.flung.dev`. |
| How do I authenticate? | Send `Authorization: Bearer `: an API key (`flung_sk_...`), an OAuth access token, or, for the few calls a connect panel makes, a connect token (`flung_ct_...`). See [Authentication](https://docs.flung.dev/authentication.md). |
| What format? | JSON in and out, except uploading a file's bytes. Times are ISO 8601 in UTC. |
| What does an error look like? | One shape for every failure, with a stable `code`. See [Errors](https://docs.flung.dev/reference/api/errors.md). |
| Is it safe to retry a POST? | Yes, with an `Idempotency-Key` header: the same key returns the first answer instead of doing the work twice. The SDK sends one on every POST. |
| How fast may I call it? | 600 requests a minute per API key or OAuth app. Past that the answer is `429` with `Retry-After`. |
| Is there a typed client? | [`@flung.dev/sdk`](https://docs.flung.dev/reference/sdk.md): every endpoint below as a typed method. |
| Where is the machine-readable form? | `https://api.flung.dev/openapi.json` (OpenAPI 3.1), also at `https://docs.flung.dev/openapi.json`. |

## Endpoints

67 endpoints, grouped by what they do.

### Posts

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/posts`](https://docs.flung.dev/reference/api/get-v1-posts.md) | List posts |
| POST | [`/v1/posts`](https://docs.flung.dev/reference/api/post-v1-posts.md) | Schedule a post |
| GET | [`/v1/posts/{id}`](https://docs.flung.dev/reference/api/get-v1-posts-id.md) | Get a post |
| PATCH | [`/v1/posts/{id}`](https://docs.flung.dev/reference/api/patch-v1-posts-id.md) | Change a scheduled post |
| DELETE | [`/v1/posts/{id}`](https://docs.flung.dev/reference/api/delete-v1-posts-id.md) | Delete a post |
| POST | [`/v1/posts/{id}/cancel`](https://docs.flung.dev/reference/api/post-v1-posts-id-cancel.md) | Cancel a scheduled post |
| POST | [`/v1/posts/{id}/retry`](https://docs.flung.dev/reference/api/post-v1-posts-id-retry.md) | Retry the failed accounts of a post |
| GET | [`/v1/posts/{id}/metrics`](https://docs.flung.dev/reference/api/get-v1-posts-id-metrics.md) | Read a post's counts |

### Accounts

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/accounts`](https://docs.flung.dev/reference/api/get-v1-accounts.md) | List connected accounts |
| GET | [`/v1/accounts/{id}`](https://docs.flung.dev/reference/api/get-v1-accounts-id.md) | Get a connected account |
| PATCH | [`/v1/accounts/{id}`](https://docs.flung.dev/reference/api/patch-v1-accounts-id.md) | Change an account's settings |
| DELETE | [`/v1/accounts/{id}`](https://docs.flung.dev/reference/api/delete-v1-accounts-id.md) | Disconnect an account |
| POST | [`/v1/accounts/{id}/refresh`](https://docs.flung.dev/reference/api/post-v1-accounts-id-refresh.md) | Refresh an account now |
| GET | [`/v1/accounts/{id}/boards`](https://docs.flung.dev/reference/api/get-v1-accounts-id-boards.md) | List Pinterest boards |
| GET | [`/v1/accounts/{id}/posting-options`](https://docs.flung.dev/reference/api/get-v1-accounts-id-posting-options.md) | Read what a post may choose |
| POST | [`/v1/connect-tokens`](https://docs.flung.dev/reference/api/post-v1-connect-tokens.md) | Make a connect token for a page |
| POST | [`/v1/connect/{platform}`](https://docs.flung.dev/reference/api/post-v1-connect-platform.md) | Start connecting an account |
| POST | [`/v1/accounts/{id}/reconnect`](https://docs.flung.dev/reference/api/post-v1-accounts-id-reconnect.md) | Start reconnecting an account |
| GET | [`/v1/connect-sessions/{id}`](https://docs.flung.dev/reference/api/get-v1-connect-sessions-id.md) | Get a connect session |

### Webhooks

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/webhook-endpoints`](https://docs.flung.dev/reference/api/get-v1-webhook-endpoints.md) | List webhook endpoints |
| POST | [`/v1/webhook-endpoints`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints.md) | Add a webhook endpoint |
| POST | [`/v1/webhook-endpoints/{id}/rotate-secret`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints-id-rotate-secret.md) | Rotate a signing secret |
| PATCH | [`/v1/webhook-endpoints/{id}`](https://docs.flung.dev/reference/api/patch-v1-webhook-endpoints-id.md) | Change an endpoint |
| DELETE | [`/v1/webhook-endpoints/{id}`](https://docs.flung.dev/reference/api/delete-v1-webhook-endpoints-id.md) | Delete a webhook endpoint |
| POST | [`/v1/webhook-endpoints/{id}/ping`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints-id-ping.md) | Send a test event |
| GET | [`/v1/webhook-endpoints/{id}/deliveries`](https://docs.flung.dev/reference/api/get-v1-webhook-endpoints-id-deliveries.md) | List deliveries |

### API keys

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/api-keys`](https://docs.flung.dev/reference/api/get-v1-api-keys.md) | List API keys |
| POST | [`/v1/api-keys`](https://docs.flung.dev/reference/api/post-v1-api-keys.md) | Create an API key |
| DELETE | [`/v1/api-keys/{id}`](https://docs.flung.dev/reference/api/delete-v1-api-keys-id.md) | Revoke an API key |

### Members

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/me`](https://docs.flung.dev/reference/api/get-v1-me.md) | Who is calling |
| GET | [`/v1/members`](https://docs.flung.dev/reference/api/get-v1-members.md) | List members |
| POST | [`/v1/members`](https://docs.flung.dev/reference/api/post-v1-members.md) | Add a member |
| DELETE | [`/v1/members/{userId}`](https://docs.flung.dev/reference/api/delete-v1-members-userid.md) | Remove a member |

### Media

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/media`](https://docs.flung.dev/reference/api/get-v1-media.md) | List files |
| POST | [`/v1/media`](https://docs.flung.dev/reference/api/post-v1-media.md) | Start an upload |
| PUT | [`/v1/media/{id}/parts/{partNumber}`](https://docs.flung.dev/reference/api/put-v1-media-id-parts-partnumber.md) | Upload one part |
| POST | [`/v1/media/{id}/complete`](https://docs.flung.dev/reference/api/post-v1-media-id-complete.md) | Finish a multipart upload |
| PUT | [`/v1/media/{id}/content`](https://docs.flung.dev/reference/api/put-v1-media-id-content.md) | Upload the bytes |
| POST | [`/v1/media/import`](https://docs.flung.dev/reference/api/post-v1-media-import.md) | Import a file from a URL |
| GET | [`/v1/media/{id}`](https://docs.flung.dev/reference/api/get-v1-media-id.md) | Get a file |
| DELETE | [`/v1/media/{id}`](https://docs.flung.dev/reference/api/delete-v1-media-id.md) | Delete a file |

### Templates

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/templates`](https://docs.flung.dev/reference/api/get-v1-templates.md) | List templates |
| POST | [`/v1/templates`](https://docs.flung.dev/reference/api/post-v1-templates.md) | Create a template |
| GET | [`/v1/templates/{id}`](https://docs.flung.dev/reference/api/get-v1-templates-id.md) | Get a template |
| PATCH | [`/v1/templates/{id}`](https://docs.flung.dev/reference/api/patch-v1-templates-id.md) | Change a template |
| DELETE | [`/v1/templates/{id}`](https://docs.flung.dev/reference/api/delete-v1-templates-id.md) | Delete a template |
| POST | [`/v1/templates/{id}/deploy`](https://docs.flung.dev/reference/api/post-v1-templates-id-deploy.md) | Schedule a post from a template |

### Variables

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/variables`](https://docs.flung.dev/reference/api/get-v1-variables.md) | List variables |
| PUT | [`/v1/variables/{name}`](https://docs.flung.dev/reference/api/put-v1-variables-name.md) | Set a variable |
| DELETE | [`/v1/variables/{name}`](https://docs.flung.dev/reference/api/delete-v1-variables-name.md) | Delete a variable |

### Feeds

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/feeds`](https://docs.flung.dev/reference/api/get-v1-feeds.md) | List feeds |
| POST | [`/v1/feeds`](https://docs.flung.dev/reference/api/post-v1-feeds.md) | Follow an RSS or Atom feed |
| PATCH | [`/v1/feeds/{id}`](https://docs.flung.dev/reference/api/patch-v1-feeds-id.md) | Change a feed |
| DELETE | [`/v1/feeds/{id}`](https://docs.flung.dev/reference/api/delete-v1-feeds-id.md) | Stop following a feed |

### Platforms

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/platforms`](https://docs.flung.dev/reference/api/get-v1-platforms.md) | List platforms and their limits |
| GET | [`/v1/posting-times`](https://docs.flung.dev/reference/api/get-v1-posting-times.md) | Suggest when to post |

### Analytics

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/usage`](https://docs.flung.dev/reference/api/get-v1-usage.md) | API requests per credential per day |
| GET | [`/v1/stats`](https://docs.flung.dev/reference/api/get-v1-stats.md) | Workspace statistics |

### Inbox

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/inbox`](https://docs.flung.dev/reference/api/get-v1-inbox.md) | List comments and mentions |
| GET | [`/v1/inbox/{id}`](https://docs.flung.dev/reference/api/get-v1-inbox-id.md) | Get an inbox item |
| PATCH | [`/v1/inbox/{id}`](https://docs.flung.dev/reference/api/patch-v1-inbox-id.md) | Mark an item read, unread or archived |
| POST | [`/v1/inbox/{id}/reply`](https://docs.flung.dev/reference/api/post-v1-inbox-id-reply.md) | Reply publicly |
| POST | [`/v1/accounts/{id}/inbox/sync`](https://docs.flung.dev/reference/api/post-v1-accounts-id-inbox-sync.md) | Read an account's inbox now |

### Experience

| Method | Path | What it does |
|---|---|---|
| GET | [`/v1/experience`](https://docs.flung.dev/reference/api/get-v1-experience.md) | Read XP, level and achievements |
| GET | [`/v1/leaderboard`](https://docs.flung.dev/reference/api/get-v1-leaderboard.md) | Read the leaderboard |
| PUT | [`/v1/leaderboard/entry`](https://docs.flung.dev/reference/api/put-v1-leaderboard-entry.md) | Join the leaderboard |
| DELETE | [`/v1/leaderboard/entry`](https://docs.flung.dev/reference/api/delete-v1-leaderboard-entry.md) | Leave the leaderboard |

## See also

| Doc | Why |
|---|---|
| [Schemas](https://docs.flung.dev/reference/api/schemas.md) | Every request and response type |
| [Errors](https://docs.flung.dev/reference/api/errors.md) | Every error code |
| [TypeScript SDK](https://docs.flung.dev/reference/sdk.md) | The same calls, typed, with retries |
| [MCP tools](https://docs.flung.dev/reference/mcp.md) | The same operations for AI agents |

---

# TypeScript SDK

> Page: https://docs.flung.dev/reference/sdk. Index of every page: https://docs.flung.dev/llms.txt

`@flung.dev/sdk` is the typed client for the Flung API: every endpoint as a method, with retries, idempotency keys and typed errors. It runs anywhere with `fetch` and Web Crypto: Node 20 or newer, Cloudflare Workers, Deno and Bun.

## TL;DR

| Question | Answer |
|---|---|
| How do I install it? | `npm install @flung.dev/sdk` |
| Which version is this page for? | `0.1.1` |
| Where does it run? | On a server. The API key it takes must never reach a browser; a page uses a connect token and the connect panel instead. |
| What does it depend on? | Nothing at run time. |
| How are methods named? | After the endpoint: `POST /v1/posts` is `flung.posts.create()`. Path, query and body fields go in one object. |

## Install

```bash
npm install @flung.dev/sdk
```

## The client

The Flung API client. Every resource of the API is a property: `flung.posts.create({ content, accountIds })`. Failed calls throw `FlungError`. Network failures, timeouts, 429 and 5xx answers are retried with backoff, honouring `Retry-After`. Every POST carries an `Idempotency-Key` (generated unless you pass one), so a retry never schedules a post twice.

### Options

| Option | Type | Required | Description |
|---|---|---|---|
| `apiKey` | `string` | yes | The credential: an API key (`flung_sk_...`), or an OAuth access token an app received through "Connect with Flung" (see Authentication in the docs). Keep either on a server; never ship it to a browser. |
| `baseUrl` | `string` | no | Where the API lives. Defaults to production; pass `https://api-preview.flung.dev` to test against preview. |
| `maxRetries` | `number` | no | Attempts after the first for network failures, timeouts, 429 and 5xx answers. Default 2. |
| `timeoutMs` | `number` | no | How long one attempt may take before it is abandoned (and retried). Default 60 seconds. |
| `fetch` | `typeof fetch` | no | A `fetch` to use instead of the global one, such as a test double. |

### Helpers

Beyond one method per endpoint, the client has these.

#### `uploadMedia(data: Blob \| Uint8Array \| ArrayBuffer, options: { contentType: string; filename?: string })`

Uploads a file in one call and resolves with it `ready`, for `mediaIds`: reserves it, then sends the bytes, in parts when it is over 100 MB. Flung reads the real type from the file itself; `contentType` is what you expect.

```ts
const image = await flung.uploadMedia(await readFile('launch.png'), { contentType: 'image/png', filename: 'launch.png' })
await flung.posts.create({ content: 'Launching today.', accountIds, mediaIds: [image.id] })
```

#### `waitForConnection(sessionId: string, options: { intervalMs?: number; timeoutMs?: number } = {})`

Waits until the person finishes a connect or reconnect: resolves with the session once it is `connected`, `failed` or `expired`. Sessions last ten minutes, so this never waits longer than that unless `timeoutMs` says so.

```ts
const session = await flung.accounts.connect({ platform: 'linkedin' })
console.warn(`Open ${session.authorizationUrl}`)
const done = await flung.waitForConnection(session.id)
```

#### `paginate(list: (params: P & { startingAfter?: string }) => Promise<{ data: T[]; hasMore: boolean }>, params = {} as P)`

Walks every page of a list: `for await (const post of flung.paginate((page) => flung.posts.list(page)))`. Pass the first page's parameters (such as `limit`) as the second argument.

## Errors

What every failed call throws. Branch on `code`, which never changes; `message` is for people and may.

```ts
try { await flung.posts.create({ content, accountIds }) }
catch (error) { if (error instanceof FlungError && error.code === 'account_needs_reconnect') ... }
```

Every code is listed in [Errors](https://docs.flung.dev/reference/api/errors.md).

## Webhooks

Checks a webhook from Flung and returns the parsed event, or throws `FlungWebhookError`. Flung signs to the Standard Webhooks spec, so any Standard Webhooks library verifies its events too.

```ts
export async function POST(request: Request): Promise {
  const body = await request.text()
  const event = await verifyWebhook({ body, headers: request.headers, secret: process.env.FLUNG_WEBHOOK_SECRET! })
  if (event.type === 'post.failed') console.warn(`Post ${event.data.post.id} failed`)
  return new Response(null, { status: 204 })
}
```

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `body` | `string` | yes | The raw request body, exactly as received. Parsing and re-serialising it changes the bytes and breaks the signature. |
| `headers` | `HeaderSource` | yes | The request's headers, as a Fetch `Headers` or Node's `req.headers`; any casing works. |
| `secret` | `string \| readonly string[]` | yes | The endpoint's `whsec_` secret. Pass two while you switch to a rotated secret; either one is accepted. |
| `nowSeconds` | `number` | no | The current time in Unix seconds, for tests. |

## Every method

### `flung.posts`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.posts.list()` | [`GET /v1/posts`](https://docs.flung.dev/reference/api/get-v1-posts.md) | List posts |
| `flung.posts.create()` | [`POST /v1/posts`](https://docs.flung.dev/reference/api/post-v1-posts.md) | Schedule a post |
| `flung.posts.get()` | [`GET /v1/posts/{id}`](https://docs.flung.dev/reference/api/get-v1-posts-id.md) | Get a post |
| `flung.posts.update()` | [`PATCH /v1/posts/{id}`](https://docs.flung.dev/reference/api/patch-v1-posts-id.md) | Change a scheduled post |
| `flung.posts.delete()` | [`DELETE /v1/posts/{id}`](https://docs.flung.dev/reference/api/delete-v1-posts-id.md) | Delete a post |
| `flung.posts.cancel()` | [`POST /v1/posts/{id}/cancel`](https://docs.flung.dev/reference/api/post-v1-posts-id-cancel.md) | Cancel a scheduled post |
| `flung.posts.retry()` | [`POST /v1/posts/{id}/retry`](https://docs.flung.dev/reference/api/post-v1-posts-id-retry.md) | Retry the failed accounts of a post |
| `flung.posts.metrics()` | [`GET /v1/posts/{id}/metrics`](https://docs.flung.dev/reference/api/get-v1-posts-id-metrics.md) | Read a post's counts |

### `flung.accounts`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.accounts.list()` | [`GET /v1/accounts`](https://docs.flung.dev/reference/api/get-v1-accounts.md) | List connected accounts |
| `flung.accounts.get()` | [`GET /v1/accounts/{id}`](https://docs.flung.dev/reference/api/get-v1-accounts-id.md) | Get a connected account |
| `flung.accounts.update()` | [`PATCH /v1/accounts/{id}`](https://docs.flung.dev/reference/api/patch-v1-accounts-id.md) | Change an account's settings |
| `flung.accounts.disconnect()` | [`DELETE /v1/accounts/{id}`](https://docs.flung.dev/reference/api/delete-v1-accounts-id.md) | Disconnect an account |
| `flung.accounts.refresh()` | [`POST /v1/accounts/{id}/refresh`](https://docs.flung.dev/reference/api/post-v1-accounts-id-refresh.md) | Refresh an account now |
| `flung.accounts.boards()` | [`GET /v1/accounts/{id}/boards`](https://docs.flung.dev/reference/api/get-v1-accounts-id-boards.md) | List Pinterest boards |
| `flung.accounts.postingOptions()` | [`GET /v1/accounts/{id}/posting-options`](https://docs.flung.dev/reference/api/get-v1-accounts-id-posting-options.md) | Read what a post may choose |
| `flung.accounts.connect()` | [`POST /v1/connect/{platform}`](https://docs.flung.dev/reference/api/post-v1-connect-platform.md) | Start connecting an account |
| `flung.accounts.reconnect()` | [`POST /v1/accounts/{id}/reconnect`](https://docs.flung.dev/reference/api/post-v1-accounts-id-reconnect.md) | Start reconnecting an account |
| `flung.accounts.syncInbox()` | [`POST /v1/accounts/{id}/inbox/sync`](https://docs.flung.dev/reference/api/post-v1-accounts-id-inbox-sync.md) | Read an account's inbox now |

### `flung.webhookEndpoints`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.webhookEndpoints.list()` | [`GET /v1/webhook-endpoints`](https://docs.flung.dev/reference/api/get-v1-webhook-endpoints.md) | List webhook endpoints |
| `flung.webhookEndpoints.create()` | [`POST /v1/webhook-endpoints`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints.md) | Add a webhook endpoint |
| `flung.webhookEndpoints.rotateSecret()` | [`POST /v1/webhook-endpoints/{id}/rotate-secret`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints-id-rotate-secret.md) | Rotate a signing secret |
| `flung.webhookEndpoints.update()` | [`PATCH /v1/webhook-endpoints/{id}`](https://docs.flung.dev/reference/api/patch-v1-webhook-endpoints-id.md) | Change an endpoint |
| `flung.webhookEndpoints.delete()` | [`DELETE /v1/webhook-endpoints/{id}`](https://docs.flung.dev/reference/api/delete-v1-webhook-endpoints-id.md) | Delete a webhook endpoint |
| `flung.webhookEndpoints.ping()` | [`POST /v1/webhook-endpoints/{id}/ping`](https://docs.flung.dev/reference/api/post-v1-webhook-endpoints-id-ping.md) | Send a test event |
| `flung.webhookEndpoints.deliveries()` | [`GET /v1/webhook-endpoints/{id}/deliveries`](https://docs.flung.dev/reference/api/get-v1-webhook-endpoints-id-deliveries.md) | List deliveries |

### `flung.connectTokens`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.connectTokens.create()` | [`POST /v1/connect-tokens`](https://docs.flung.dev/reference/api/post-v1-connect-tokens.md) | Make a connect token for a page |

### `flung.connectSessions`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.connectSessions.get()` | [`GET /v1/connect-sessions/{id}`](https://docs.flung.dev/reference/api/get-v1-connect-sessions-id.md) | Get a connect session |

### `flung.apiKeys`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.apiKeys.list()` | [`GET /v1/api-keys`](https://docs.flung.dev/reference/api/get-v1-api-keys.md) | List API keys |
| `flung.apiKeys.create()` | [`POST /v1/api-keys`](https://docs.flung.dev/reference/api/post-v1-api-keys.md) | Create an API key |
| `flung.apiKeys.revoke()` | [`DELETE /v1/api-keys/{id}`](https://docs.flung.dev/reference/api/delete-v1-api-keys-id.md) | Revoke an API key |

### `flung.me`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.me.get()` | [`GET /v1/me`](https://docs.flung.dev/reference/api/get-v1-me.md) | Who is calling |

### `flung.members`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.members.list()` | [`GET /v1/members`](https://docs.flung.dev/reference/api/get-v1-members.md) | List members |
| `flung.members.add()` | [`POST /v1/members`](https://docs.flung.dev/reference/api/post-v1-members.md) | Add a member |
| `flung.members.remove()` | [`DELETE /v1/members/{userId}`](https://docs.flung.dev/reference/api/delete-v1-members-userid.md) | Remove a member |

### `flung.media`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.media.list()` | [`GET /v1/media`](https://docs.flung.dev/reference/api/get-v1-media.md) | List files |
| `flung.media.create()` | [`POST /v1/media`](https://docs.flung.dev/reference/api/post-v1-media.md) | Start an upload |
| `flung.media.uploadPart()` | [`PUT /v1/media/{id}/parts/{partNumber}`](https://docs.flung.dev/reference/api/put-v1-media-id-parts-partnumber.md) | Upload one part |
| `flung.media.complete()` | [`POST /v1/media/{id}/complete`](https://docs.flung.dev/reference/api/post-v1-media-id-complete.md) | Finish a multipart upload |
| `flung.media.upload()` | [`PUT /v1/media/{id}/content`](https://docs.flung.dev/reference/api/put-v1-media-id-content.md) | Upload the bytes |
| `flung.media.import()` | [`POST /v1/media/import`](https://docs.flung.dev/reference/api/post-v1-media-import.md) | Import a file from a URL |
| `flung.media.get()` | [`GET /v1/media/{id}`](https://docs.flung.dev/reference/api/get-v1-media-id.md) | Get a file |
| `flung.media.delete()` | [`DELETE /v1/media/{id}`](https://docs.flung.dev/reference/api/delete-v1-media-id.md) | Delete a file |

### `flung.templates`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.templates.list()` | [`GET /v1/templates`](https://docs.flung.dev/reference/api/get-v1-templates.md) | List templates |
| `flung.templates.create()` | [`POST /v1/templates`](https://docs.flung.dev/reference/api/post-v1-templates.md) | Create a template |
| `flung.templates.get()` | [`GET /v1/templates/{id}`](https://docs.flung.dev/reference/api/get-v1-templates-id.md) | Get a template |
| `flung.templates.update()` | [`PATCH /v1/templates/{id}`](https://docs.flung.dev/reference/api/patch-v1-templates-id.md) | Change a template |
| `flung.templates.delete()` | [`DELETE /v1/templates/{id}`](https://docs.flung.dev/reference/api/delete-v1-templates-id.md) | Delete a template |
| `flung.templates.deploy()` | [`POST /v1/templates/{id}/deploy`](https://docs.flung.dev/reference/api/post-v1-templates-id-deploy.md) | Schedule a post from a template |

### `flung.variables`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.variables.list()` | [`GET /v1/variables`](https://docs.flung.dev/reference/api/get-v1-variables.md) | List variables |
| `flung.variables.set()` | [`PUT /v1/variables/{name}`](https://docs.flung.dev/reference/api/put-v1-variables-name.md) | Set a variable |
| `flung.variables.delete()` | [`DELETE /v1/variables/{name}`](https://docs.flung.dev/reference/api/delete-v1-variables-name.md) | Delete a variable |

### `flung.feeds`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.feeds.list()` | [`GET /v1/feeds`](https://docs.flung.dev/reference/api/get-v1-feeds.md) | List feeds |
| `flung.feeds.create()` | [`POST /v1/feeds`](https://docs.flung.dev/reference/api/post-v1-feeds.md) | Follow an RSS or Atom feed |
| `flung.feeds.update()` | [`PATCH /v1/feeds/{id}`](https://docs.flung.dev/reference/api/patch-v1-feeds-id.md) | Change a feed |
| `flung.feeds.delete()` | [`DELETE /v1/feeds/{id}`](https://docs.flung.dev/reference/api/delete-v1-feeds-id.md) | Stop following a feed |

### `flung.platforms`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.platforms.list()` | [`GET /v1/platforms`](https://docs.flung.dev/reference/api/get-v1-platforms.md) | List platforms and their limits |
| `flung.platforms.postingTimes()` | [`GET /v1/posting-times`](https://docs.flung.dev/reference/api/get-v1-posting-times.md) | Suggest when to post |

### `flung.analytics`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.analytics.usage()` | [`GET /v1/usage`](https://docs.flung.dev/reference/api/get-v1-usage.md) | API requests per credential per day |
| `flung.analytics.stats()` | [`GET /v1/stats`](https://docs.flung.dev/reference/api/get-v1-stats.md) | Workspace statistics |

### `flung.inbox`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.inbox.list()` | [`GET /v1/inbox`](https://docs.flung.dev/reference/api/get-v1-inbox.md) | List comments and mentions |
| `flung.inbox.get()` | [`GET /v1/inbox/{id}`](https://docs.flung.dev/reference/api/get-v1-inbox-id.md) | Get an inbox item |
| `flung.inbox.update()` | [`PATCH /v1/inbox/{id}`](https://docs.flung.dev/reference/api/patch-v1-inbox-id.md) | Mark an item read, unread or archived |
| `flung.inbox.reply()` | [`POST /v1/inbox/{id}/reply`](https://docs.flung.dev/reference/api/post-v1-inbox-id-reply.md) | Reply publicly |

### `flung.experience`

| Method | Endpoint | What it does |
|---|---|---|
| `flung.experience.get()` | [`GET /v1/experience`](https://docs.flung.dev/reference/api/get-v1-experience.md) | Read XP, level and achievements |
| `flung.experience.leaderboard()` | [`GET /v1/leaderboard`](https://docs.flung.dev/reference/api/get-v1-leaderboard.md) | Read the leaderboard |
| `flung.experience.joinLeaderboard()` | [`PUT /v1/leaderboard/entry`](https://docs.flung.dev/reference/api/put-v1-leaderboard-entry.md) | Join the leaderboard |
| `flung.experience.leaveLeaderboard()` | [`DELETE /v1/leaderboard/entry`](https://docs.flung.dev/reference/api/delete-v1-leaderboard-entry.md) | Leave the leaderboard |

## See also

| Doc | Why |
|---|---|
| [Connect panel](https://docs.flung.dev/reference/connect.md) | Let people connect accounts from your page |
| [API reference](https://docs.flung.dev/reference/api.md) | Every endpoint, with samples |
| [Webhooks](https://docs.flung.dev/webhooks.md) | Receiving and verifying events |

---

# Connect panel

> Page: https://docs.flung.dev/reference/connect. Index of every page: https://docs.flung.dev/llms.txt

`@flung.dev/connect` gives your page ``, a panel where people connect, reconnect and disconnect their social accounts, and `` for React. It takes a connect token your server makes, never an API key.

## TL;DR

| Question | Answer |
|---|---|
| How do I install it? | `npm install @flung.dev/connect` |
| Which version is this page for? | `0.1.1` |
| What does it need? | A connect token from `POST /v1/connect-tokens`, made on your server for your page's origin. See [Authentication](https://docs.flung.dev/authentication.md). |
| What does it depend on? | Nothing at run time. React is an optional peer, for the React component. |
| Which entry points are there? | `@flung.dev/connect/register` registers the element, `@flung.dev/connect/react` is the React component, and `@flung.dev/connect/core` has the calls with no element. |

## Properties

The React component takes these. The element takes the same as attributes (`base-url`, `platforms`, `mode`, `theme`) and properties (`token`, `labels`).

| Prop | Type | Required | Description |
|---|---|---|---|
| `token` | `TokenSource \| null` | yes | A connect token from your server, or a function that fetches a fresh one. |
| `baseUrl` | `string \| undefined` | no | Where the API lives; production when left out. |
| `platforms` | `readonly Platform[] \| undefined` | no | Offer only these platforms. |
| `mode` | `'popup' \| 'redirect' \| undefined` | no | `redirect` sends the whole page to the platform and back, for pages that cannot open windows. |
| `theme` | `'light' \| 'dark' \| undefined` | no | `light` or `dark`. Follows the page's colour scheme when left out. |
| `labels` | `Partial \| undefined` | no | Any of the panel's words, to put it in another language. |
| `className` | `string \| undefined` | no | A class for the element itself. |
| `style` | `CSSProperties \| undefined` | no | Styles for the element, such as its `--flung-*` custom properties. |
| `onLoad` | `((detail: LoadDetail) => void) \| undefined` | no | The accounts were read: on first show and after every change. |
| `onConnect` | `((detail: ConnectDetail) => void) \| undefined` | no | A connect or reconnect finished. |
| `onDisconnect` | `((detail: DisconnectDetail) => void) \| undefined` | no | An account was disconnected. |
| `onError` | `((detail: ErrorDetail) => void) \| undefined` | no | A call or a connect failed. |

## Events

The element sends these, bubbling and crossing its shadow root. The React component takes them as callbacks.

### `flung-load`

`flung-load`: the accounts were read, on first show and after every change.

| Field | Type | Required | Description |
|---|---|---|---|
| `accounts` | `Account[]` | yes | The connected accounts the panel shows, newest first. |

### `flung-connect`

`flung-connect`: a connect or reconnect finished. `accounts` are the ones stored; Facebook stores one per page.

| Field | Type | Required | Description |
|---|---|---|---|
| `session` | `ConnectSession \| null` | yes | The connect session, or null when the person came back from a redirect. |
| `accounts` | `Account[]` | yes | The accounts stored. |

### `flung-disconnect`

`flung-disconnect`: an account was disconnected.

| Field | Type | Required | Description |
|---|---|---|---|
| `account` | `Account` | yes | The account, now `disconnected`. |

### `flung-error`

`flung-error`: a call or a connect failed. `code` is the API's stable code, such as `forbidden` or `access_denied`.

| Field | Type | Required | Description |
|---|---|---|---|
| `code` | `string` | yes | The API's stable code, or the platform's, such as `access_denied`. |
| `message` | `string` | yes | What went wrong, for a person. |

## Styling

Set any of these custom properties on the element, or style its parts with `::part()`.

| Custom property |
|---|
| `--flung-font` |
| `--flung-text` |
| `--flung-muted` |
| `--flung-surface` |
| `--flung-raised` |
| `--flung-border` |
| `--flung-accent` |
| `--flung-accent-text` |
| `--flung-danger` |
| `--flung-warning` |
| `--flung-radius` |

| Part |
|---|
| `::part(list)` |
| `::part(message)` |
| `::part(panel)` |
| `::part(badge)` |
| `::part(name)` |
| `::part(platform)` |
| `::part(account)` |
| `::part(status)` |
| `::part(form)` |
| `::part(note)` |
| `::part(confirm)` |

## Words

Every word the panel shows. Pass `labels` with any of these keys to change them; `{platform}`, `{account}`, `{message}` and `{date}` are filled in.

| Key | Default |
|---|---|
| `panel` | Connected accounts |
| `connect` | Connect |
| `connectAnother` | Add another |
| `reconnect` | Reconnect |
| `disconnect` | Disconnect |
| `confirmDisconnect` | Disconnect {account}? Posts to it will stop. |
| `keep` | Keep it |
| `cancel` | Cancel |
| `continue` | Continue |
| `handleBluesky` | Your Bluesky handle, such as alice.bsky.social (optional) |
| `handleMastodon` | Your Mastodon server, such as mastodon.social |
| `waiting` | Finish signing in to {platform} in the window that opened. |
| `blocked` | Your browser did not open the {platform} window. |
| `openSignIn` | Open {platform} sign-in |
| `connected` | {platform} is connected. |
| `failed` | {platform} was not connected: {message} |
| `expired` | Signing in to {platform} took too long. Try again. |
| `disconnected` | {account} is disconnected. |
| `needsReconnect` | Needs reconnecting |
| `reconnectBy` | Reconnect by {date} |
| `loading` | Loading accounts… |
| `loadFailed` | Could not load accounts: {message} |
| `empty` | There is nothing to connect here. |
| `noToken` | This panel has no connect token yet. |

## Without the element

`@flung.dev/connect/core` has `ConnectClient`, for a page that draws its own panel.

| Method | What it does |
|---|---|
| `platforms()` | Every platform, and whether this Flung can connect it. |
| `accounts()` | The connected accounts the token may see: its profile's, or the whole workspace's. |
| `connect(platform: Platform, options: { handle?: string \| undefined; redirectUrl?: string \| undefined } = {})` | Starts connecting a new account. `handle` is the Bluesky handle, or the Mastodon instance (required there). |
| `reconnect(accountId: string, options: { redirectUrl?: string \| undefined } = {})` | Starts reconnecting an account that needs it; the person must sign in as the same platform user. |
| `disconnect(accountId: string)` | Deletes the account's stored tokens. It stays listed as `disconnected`. |
| `session(id: string)` | A connect session's state: pending, connected, failed or expired. |
| `waitForSession(id: string, options: { signal?: AbortSignal \| undefined; intervalMs?: number \| undefined } = {})` | Resolves once the session is no longer `pending`. Rejects with an `AbortError` when `signal` aborts. |

## See also

| Doc | Why |
|---|---|
| [Add account connecting to your site](https://docs.flung.dev/connect-accounts.md) | The whole flow, server and page |
| [Create a connect token](https://docs.flung.dev/reference/api/post-v1-connect-tokens.md) | The endpoint your server calls |
| [TypeScript SDK](https://docs.flung.dev/reference/sdk.md) | Making the token from your server |

---

# MCP tools

> Page: https://docs.flung.dev/reference/mcp. Index of every page: https://docs.flung.dev/llms.txt

The 28 tools Flung's MCP server gives an AI agent: connect and reconnect social accounts, schedule and retry posts, read and answer comments, and automate posting.

## TL;DR

| Question | Answer |
|---|---|
| Where is the server? | `https://mcp.flung.dev/mcp-oauth` signs a person in through their browser; `https://mcp.flung.dev/mcp` takes an API key. Both speak Streamable HTTP. |
| Where do I start? | `find_accounts_needing_attention`, then `schedule_post`. |
| Can an agent connect an account alone? | No. A connect returns the platform's consent link; a person opens it and approves. |
| Is there a machine-readable list? | `https://docs.flung.dev/tools.json` has every tool with its JSON Schema. |

## Accounts

| Tool | What it does | Changes things? |
|---|---|---|
| [`find_accounts_needing_attention`](https://docs.flung.dev/reference/mcp/find-accounts-needing-attention.md) | Find accounts that need a person | Reads only |
| [`list_accounts`](https://docs.flung.dev/reference/mcp/list-accounts.md) | List connected accounts | Reads only |
| [`get_account`](https://docs.flung.dev/reference/mcp/get-account.md) | Get an account | Reads only |
| [`connect_account`](https://docs.flung.dev/reference/mcp/connect-account.md) | Connect a new account | Changes something |
| [`reconnect_account`](https://docs.flung.dev/reference/mcp/reconnect-account.md) | Reconnect an account | Changes something |
| [`get_connect_session`](https://docs.flung.dev/reference/mcp/get-connect-session.md) | Check a connect session | Reads only |
| [`refresh_account`](https://docs.flung.dev/reference/mcp/refresh-account.md) | Refresh an account now | Changes something; safe to repeat |
| [`disconnect_account`](https://docs.flung.dev/reference/mcp/disconnect-account.md) | Disconnect an account | Changes or removes something |
| [`get_posting_options`](https://docs.flung.dev/reference/mcp/get-posting-options.md) | Read a TikTok account's posting options | Reads only |

## Posts and media

| Tool | What it does | Changes things? |
|---|---|---|
| [`schedule_post`](https://docs.flung.dev/reference/mcp/schedule-post.md) | Schedule a post | Changes something |
| [`update_post`](https://docs.flung.dev/reference/mcp/update-post.md) | Change a scheduled post | Changes something; safe to repeat |
| [`cancel_post`](https://docs.flung.dev/reference/mcp/cancel-post.md) | Cancel a scheduled post | Changes or removes something |
| [`retry_post`](https://docs.flung.dev/reference/mcp/retry-post.md) | Retry a post's failed accounts | Changes something |
| [`import_media`](https://docs.flung.dev/reference/mcp/import-media.md) | Add an image or video from a URL | Changes something |
| [`get_post`](https://docs.flung.dev/reference/mcp/get-post.md) | Get a post | Reads only |
| [`list_posts`](https://docs.flung.dev/reference/mcp/list-posts.md) | List posts | Reads only |
| [`get_post_metrics`](https://docs.flung.dev/reference/mcp/get-post-metrics.md) | Read a post's views and likes | Reads only |

## Planning

| Tool | What it does | Changes things? |
|---|---|---|
| [`list_platforms`](https://docs.flung.dev/reference/mcp/list-platforms.md) | List platforms and their limits | Reads only |
| [`suggest_posting_times`](https://docs.flung.dev/reference/mcp/suggest-posting-times.md) | Suggest when to post | Reads only |

## Inbox

| Tool | What it does | Changes things? |
|---|---|---|
| [`list_inbox`](https://docs.flung.dev/reference/mcp/list-inbox.md) | List comments and mentions | Reads only |
| [`reply_to_inbox_item`](https://docs.flung.dev/reference/mcp/reply-to-inbox-item.md) | Reply to a comment or mention | Changes something |

## Automation

| Tool | What it does | Changes things? |
|---|---|---|
| [`list_templates`](https://docs.flung.dev/reference/mcp/list-templates.md) | List post templates | Reads only |
| [`deploy_template`](https://docs.flung.dev/reference/mcp/deploy-template.md) | Schedule a post from a template | Changes something |
| [`list_variables`](https://docs.flung.dev/reference/mcp/list-variables.md) | List workspace variables | Reads only |
| [`set_variable`](https://docs.flung.dev/reference/mcp/set-variable.md) | Set a workspace variable | Changes something; safe to repeat |
| [`list_feeds`](https://docs.flung.dev/reference/mcp/list-feeds.md) | List RSS feeds | Reads only |
| [`follow_feed`](https://docs.flung.dev/reference/mcp/follow-feed.md) | Post new items from an RSS feed | Changes something |

## Workspace

| Tool | What it does | Changes things? |
|---|---|---|
| [`get_experience`](https://docs.flung.dev/reference/mcp/get-experience.md) | Read XP, level and achievements | Reads only |

## See also

| Doc | Why |
|---|---|
| [Connect an AI agent](https://docs.flung.dev/mcp.md) | Install the server in Claude Code, Cursor, VS Code or Claude Desktop |
| [Prompts and recipes](https://docs.flung.dev/agents/prompts.md) | What to ask, and the tools an agent calls for it |
| [API reference](https://docs.flung.dev/reference/api.md) | The same operations over HTTP |

---

# Errors

> Page: https://docs.flung.dev/reference/api/errors. Index of every page: https://docs.flung.dev/llms.txt

Every failed call answers with the same JSON shape and a stable code. Branch on the code; the message is for people and may change.

## TL;DR

| Question | Answer |
|---|---|
| What does a failure look like? | `{ "error": { "code": "...", "message": "...", "details": ... } }` with a 4xx or 5xx status. |
| What should my code branch on? | `error.code`. It never changes for a given problem. |
| What does the SDK do? | It throws `FlungError` with `status`, `code`, `message` and `details`. Two codes are its own: `network_error` (no answer arrived) and `unexpected_response`. |
| Which are worth retrying? | `rate_limited` after `Retry-After`, and `internal_error` with the same idempotency key. The SDK retries these and network failures by itself. |

## Codes

22 codes.

| Code | What to do |
|---|---|
| `unauthorized` | The credential is missing, unknown, revoked or expired. Check the key, or refresh the OAuth token. |
| `forbidden` | The credential cannot do this: a connect token outside its seven calls or its page, or a key without the scope. |
| `validation_failed` | The request does not match the schema. `details` lists each problem with its path. |
| `not_found` | No such resource in this workspace, or the route does not exist. |
| `unknown_accounts` | One or more `accountIds` are not this workspace's. `details.accountIds` names them. |
| `platform_not_supported` | Flung cannot connect that platform here. |
| `media_required` | The platform refuses a post with no image or video. |
| `account_needs_reconnect` | The account's access ended. Start a reconnect and have the person approve it. |
| `not_configured` | The platform's app credentials are not set up in this environment. |
| `connect_failed` | The platform refused to start the connect. The message says why. |
| `platform_error` | The platform answered with an error. The message carries its reason. |
| `post_not_editable` | The post has started publishing or has finished, so it can no longer change. |
| `unknown_variable` | The text names a `{{variable}}` the workspace has not set. |
| `rate_limited` | Too many requests. Wait for the seconds in `Retry-After`. |
| `unsupported_media` | The file is not a type Flung accepts, judged from its first bytes. |
| `media_too_large` | The file is over the limit for its type. |
| `unknown_media` | One or more `mediaIds` are not this workspace's. |
| `media_not_ready` | A file is still uploading. Wait until its status is `ready`. |
| `text_too_long` | The text is over a platform's limit. `details` names the platform and the limit. |
| `publish_at_in_past` | `publishAt` is in the past. Omit it to publish now. |
| `idempotency_key_reused` | The `Idempotency-Key` was used before with a different request. Use a new key for new work. |
| `internal_error` | Something failed on Flung's side. Retry with the same idempotency key. |

---

# Flung for AI agents

> Page: https://docs.flung.dev/agents/overview. Index of every page: https://docs.flung.dev/llms.txt

What an AI agent needs to use Flung, and to build Flung into someone's product: which way in to choose, the order of calls, and the rules that keep posts and accounts safe.

## TL;DR

| Question | Answer |
|---|---|
| What is Flung? | An API that connects social media accounts (X, Instagram, Facebook, Threads, LinkedIn, TikTok, YouTube, Pinterest, Bluesky, Mastodon, Discord, Farcaster) and publishes posts to them, now or on a schedule. |
| I am an agent posting for a person | Use the MCP server: `https://mcp.flung.dev/mcp-oauth`. See [Connect an AI agent](https://docs.flung.dev/mcp.md). |
| I am a coding agent adding Flung to a website | Follow [Add Flung to a website](https://docs.flung.dev/agents/build.md) step by step. Every file it shows is from a working example. |
| I need every page in one file | `https://docs.flung.dev/llms-full.txt`. The index is `https://docs.flung.dev/llms.txt`, and any page is Markdown with `.md` added to its address. |
| I need the exact API | `https://docs.flung.dev/openapi.json` (OpenAPI 3.1) and `https://docs.flung.dev/tools.json` (the MCP tools with JSON Schemas). |

## Choose the way in

| The task | Use | Why |
|---|---|---|
| Post, schedule, or check accounts for a person, in a chat | The MCP server | The person signs in once in their browser; no key is handled. |
| Call Flung from a server or a script | `@flung.dev/sdk`, or the REST API | Typed, retried, idempotent. |
| Let people connect their accounts on a web page | `@flung.dev/connect` with a connect token | The API key never reaches the browser. |
| React to posts being published or failing | Webhooks | Signed events, retried until your endpoint answers. |

## What the server tells every agent

The MCP server sends this when a client connects. It holds over HTTP too.

- Flung connects social media accounts and schedules posts to them.
- To keep accounts working, start with find_accounts_needing_attention. Accounts with renewal "automatic" renew themselves; the others need a person before reconnectBy.
- Connecting or reconnecting needs a person: call connect_account or reconnect_account, give the person the authorizationUrl, then poll get_connect_session until it is not pending. The link expires in ten minutes.
- To post, call list_accounts for ids, then schedule_post with an idempotencyKey you reuse on retry, then get_post for the outcome.
- A refused call is an answer, not a fault: its text says what to do next.

## Rules

- Never put an API key (`flung_sk_...`) in a browser, a page, a commit or a log. A page gets a connect token from its own server.
- A person must approve every connect and reconnect on the platform's own page. Give them the link; never try to complete it.
- Send an `Idempotency-Key` with every POST, and reuse it when you retry the same request, or a post may go out twice.
- Ask the person before calling anything that disconnects an account, cancels a post, or replies publicly.
- Branch on `error.code`, never on the message. A refusal says what to do next.

## See also

| Doc | Why |
|---|---|
| [Add Flung to a website](https://docs.flung.dev/agents/build.md) | The recipe for coding agents |
| [Rules file](https://docs.flung.dev/agents/rules.md) | A block to paste into CLAUDE.md or AGENTS.md |
| [Prompts and recipes](https://docs.flung.dev/agents/prompts.md) | Requests that work, and the tools behind them |
| [Files for agents](https://docs.flung.dev/agents/machine-readable.md) | Every machine-readable file this site serves |

---

# Add Flung to a website

> Page: https://docs.flung.dev/agents/build. Index of every page: https://docs.flung.dev/llms.txt

A recipe for a coding agent: add account connecting and posting to a web app with Flung. Follow the steps in order; each file below is from a working example app and can be copied as it is.

## TL;DR

| Question | Answer |
|---|---|
| What will the site do? | Show a panel where its users connect and disconnect social accounts, and post to those accounts from the server. |
| What must the person give me? | A Flung API key that starts `flung_sk_`. Keep it in a server environment variable, never in code a browser loads. |
| Which frameworks? | Next.js is shown. React with Vite and a plain page follow the same two parts: a server route that makes a connect token, and the panel that uses it. |
| How do I know it works? | The panel lists the platforms; connecting one opens the platform's page and the account appears; a post to it returns a post id. |

## 1. Install

```bash
npm install @flung.dev/sdk @flung.dev/connect
```

## 2. Keep the key on the server

Put the key in the server's environment. Never give it a public prefix such as `NEXT_PUBLIC_` or `VITE_`.

**.env.local**

```bash
# A full-access Flung API key (flung_sk_...). Server only: never prefix it with NEXT_PUBLIC_.
FLUNG_API_KEY=

# Optional. The API: https://api.flung.dev (the default) or https://api-preview.flung.dev.
FLUNG_BASE_URL=

# The origin this app is served from, which connect tokens are made for. http://localhost:3000 when left out.
APP_ORIGIN=
```

## 3. Make connect tokens on the server

Add a route the page calls. It checks who is signed in (add your app's own check), then asks Flung for a short-lived connect token for this site's origin.

**app/api/connect-token/route.ts**

```ts
import { Flung, FlungError } from '@flung.dev/sdk'

/**
 * `POST /api/connect-token`: a short-lived connect token for this app's own page. The API key stays here on the server;
 * the token it makes can only list, connect, reconnect and disconnect accounts, from `APP_ORIGIN`, for fifteen minutes.
 *
 * In a real app, check who is signed in first, and pass their profile as `profileId` so each person sees only theirs.
 */
export async function POST(): Promise {
  const apiKey = process.env.FLUNG_API_KEY
  if (apiKey === undefined || apiKey === '') return Response.json({ error: 'Set FLUNG_API_KEY in .env.local.' }, { status: 500 })
  const flung = new Flung({ apiKey, ...(process.env.FLUNG_BASE_URL ? { baseUrl: process.env.FLUNG_BASE_URL } : {}) })
  try {
    const { token, expiresAt } = await flung.connectTokens.create({ origins: [process.env.APP_ORIGIN || 'http://localhost:3000'], expiresIn: 900 })
    return Response.json({ token, expiresAt }, { headers: { 'cache-control': 'no-store' } })
  } catch (error) {
    const status = error instanceof FlungError ? (error.status ?? 502) : 502
    return Response.json({ error: error instanceof Error ? error.message : 'Could not make a connect token.' }, { status })
  }
}
```

## 4. Show the panel

Add the panel to a page. It asks the route for a token, and again when one expires.

**app/accounts.tsx**

```tsx
'use client'

import { useCallback, useState } from 'react'

import { FlungConnect } from '@flung.dev/connect/react'

/** Asks this app's server for a connect token; the panel calls it again when a token expires. */
async function connectToken(): Promise {
  const response = await fetch('/api/connect-token', { method: 'POST' })
  const body = (await response.json()) as { token?: string; error?: string }
  if (!response.ok || body.token === undefined) throw new Error(body.error ?? 'Could not make a connect token.')
  return body.token
}

export function Accounts({ baseUrl }: { baseUrl: string | undefined }) {
  const [events, setEvents] = useState([])
  const log = useCallback((line: string) => setEvents((previous) => [line, ...previous].slice(0, 5)), [])
  return (
    <>
       log(`Connected ${accounts.map((account) => account.username ?? account.id).join(', ')}`)}
        onDisconnect={({ account }) => log(`Disconnected ${account.username ?? account.id}`)}
        onError={({ code, message }) => log(`${code}: ${message}`)}
      />
      {events.length > 0 && (
        
    {events.map((line, index) => (
  • {line}
  • ))}
)} ) } ``` ## 5. Post from the server With the accounts connected, post to them with the API key, from the server. **curl** ```bash curl -X POST 'https://api.flung.dev/v1/posts' \ --header "Authorization: Bearer $FLUNG_API_KEY" \ --header 'Content-Type: application/json' \ --header "Idempotency-Key: $(uuidgen)" \ --data '{ "content": "Launching today.", "accountIds": [ "acct_aaaaaaaaaaaaaaaaaaaaaaaa" ], "platformOverrides": { "x": { "content": "Launching today. #launch" } }, "variables": { "product": "Flung" } }' ``` **TypeScript** ```ts import { Flung } from '@flung.dev/sdk' const flung = new Flung({ apiKey: process.env.FLUNG_API_KEY! }) const result = await flung.posts.create({ content: 'Launching today.', accountIds: [ 'acct_aaaaaaaaaaaaaaaaaaaaaaaa', ], platformOverrides: { x: { content: 'Launching today. #launch', }, }, variables: { product: 'Flung', }, }) ``` **fetch** ```js const response = await fetch('https://api.flung.dev/v1/posts', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.FLUNG_API_KEY}`, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID(), }, body: JSON.stringify({ content: 'Launching today.', accountIds: [ 'acct_aaaaaaaaaaaaaaaaaaaaaaaa', ], platformOverrides: { x: { content: 'Launching today. #launch', }, }, variables: { product: 'Flung', }, }), }) if (!response.ok) throw new Error((await response.json()).error.message) const result = await response.json() ``` ## 6. Hear what happened Optionally, register a webhook endpoint and verify each event. ```ts export async function POST(request: Request): Promise { const body = await request.text() const event = await verifyWebhook({ body, headers: request.headers, secret: process.env.FLUNG_WEBHOOK_SECRET! }) if (event.type === 'post.failed') console.warn(`Post ${event.data.post.id} failed`) return new Response(null, { status: 204 }) } ``` ## Checks before you finish - `FLUNG_API_KEY` appears only in server code and the server's environment. - The connect-token route is behind the app's own sign-in. - `APP_ORIGIN` (or the origin you pass) is the address the page is served from, or the API refuses the panel's calls. - Every POST that creates something sends an `Idempotency-Key`; the SDK does this for you. ## Other frameworks | Framework | Read | |---|---| | React with Vite | [Add account connecting to your site](https://docs.flung.dev/connect-accounts.md#react-with-vite) | | A plain page, no framework | [Add account connecting to your site](https://docs.flung.dev/connect-accounts.md#a-plain-page) | | Anything else | Call the REST API: [API reference](https://docs.flung.dev/reference/api.md) | --- # Rules file for your agent > Page: https://docs.flung.dev/agents/rules. Index of every page: https://docs.flung.dev/llms.txt A block to paste into CLAUDE.md, AGENTS.md or an editor's rules file, so an agent uses Flung the right way. It is the guidance the MCP server sends when a client connects. ## TL;DR | Question | Answer | |---|---| | What do I paste? | The block below, as it is. | | Where does it go? | In the rules file your agent reads; the table below names it for each client. | | Does it need the MCP server? | It names the tools, so connect them first: [Connect an AI agent](https://docs.flung.dev/mcp.md). | ## The block ```markdown ## Social media with Flung Flung connects social media accounts and publishes posts to them. Use its MCP tools (or its API) for every post; never post to a platform directly. - Flung connects social media accounts and schedules posts to them. - To keep accounts working, start with find_accounts_needing_attention. Accounts with renewal "automatic" renew themselves; the others need a person before reconnectBy. - Connecting or reconnecting needs a person: call connect_account or reconnect_account, give the person the authorizationUrl, then poll get_connect_session until it is not pending. The link expires in ten minutes. - To post, call list_accounts for ids, then schedule_post with an idempotencyKey you reuse on retry, then get_post for the outcome. - A refused call is an answer, not a fault: its text says what to do next. - Never put a Flung API key in browser code or a commit; pages use a connect token from their server. Tools: find_accounts_needing_attention, list_accounts, get_account, connect_account, reconnect_account, get_connect_session, refresh_account, disconnect_account, schedule_post, update_post, cancel_post, retry_post, import_media, get_post, list_posts, list_inbox, reply_to_inbox_item, get_post_metrics, get_experience, list_platforms, suggest_posting_times, get_posting_options, list_templates, deploy_template, list_variables, set_variable, list_feeds, follow_feed. Docs for agents: https://docs.flung.dev/llms.txt ``` ## Where to put it | Client | File | |---|---| | Claude Code | `CLAUDE.md` in the project, or `~/.claude/CLAUDE.md` for every project | | Codex and most other coding agents | `AGENTS.md` in the project | | Cursor | A rule in `.cursor/rules/`, such as `.cursor/rules/flung.mdc` | | GitHub Copilot | `.github/copilot-instructions.md` | | Gemini CLI | `GEMINI.md` in the project | --- # Prompts and recipes > Page: https://docs.flung.dev/agents/prompts. Index of every page: https://docs.flung.dev/llms.txt Things to ask an agent connected to Flung, and the tools it calls for each. Every prompt below is one the tools are tested against. ## TL;DR | Question | Answer | |---|---| | Do I need to name tools? | No. Say what you want; the agent picks the tool from its description. | | What if an account needs reconnecting? | The agent gives you a link to approve on the platform, then carries on. | ## Accounts | Say | The agent calls | |---|---| | "Which of my social accounts are about to stop working?" | [`find_accounts_needing_attention`](https://docs.flung.dev/reference/mcp/find-accounts-needing-attention.md) | | "Do any accounts need reconnecting this month?" | [`find_accounts_needing_attention`](https://docs.flung.dev/reference/mcp/find-accounts-needing-attention.md) | | "Which social accounts are connected?" | [`list_accounts`](https://docs.flung.dev/reference/mcp/list-accounts.md) | | "What is my LinkedIn account id?" | [`list_accounts`](https://docs.flung.dev/reference/mcp/list-accounts.md) | | "Is my X account still connected?" | [`get_account`](https://docs.flung.dev/reference/mcp/get-account.md) | | "Connect my LinkedIn account to Flung." | [`connect_account`](https://docs.flung.dev/reference/mcp/connect-account.md) | | "Add our company Facebook page." | [`connect_account`](https://docs.flung.dev/reference/mcp/connect-account.md) | | "My Instagram stopped posting, fix it." | [`reconnect_account`](https://docs.flung.dev/reference/mcp/reconnect-account.md) | | "Reconnect the accounts that are about to expire." | [`reconnect_account`](https://docs.flung.dev/reference/mcp/reconnect-account.md) | | "Did the LinkedIn connection go through?" | [`get_connect_session`](https://docs.flung.dev/reference/mcp/get-connect-session.md) | | "Check my X account still works." | [`refresh_account`](https://docs.flung.dev/reference/mcp/refresh-account.md) | | "Disconnect our old Mastodon account." | [`disconnect_account`](https://docs.flung.dev/reference/mcp/disconnect-account.md) | | "Who can see our TikTok posts?" | [`get_posting_options`](https://docs.flung.dev/reference/mcp/get-posting-options.md) | ## Posts and media | Say | The agent calls | |---|---| | "Post "We are live" to LinkedIn and X tomorrow at 9am." | [`schedule_post`](https://docs.flung.dev/reference/mcp/schedule-post.md) | | "Schedule this announcement for Monday." | [`schedule_post`](https://docs.flung.dev/reference/mcp/schedule-post.md) | | "Move tomorrow's post to Friday at noon." | [`update_post`](https://docs.flung.dev/reference/mcp/update-post.md) | | "Cancel the launch post." | [`cancel_post`](https://docs.flung.dev/reference/mcp/cancel-post.md) | | "Try the failed post again." | [`retry_post`](https://docs.flung.dev/reference/mcp/retry-post.md) | | "Post this image to Instagram: https://example.com/launch.jpg" | [`import_media`](https://docs.flung.dev/reference/mcp/import-media.md) | | "Did yesterday's post go out everywhere?" | [`get_post`](https://docs.flung.dev/reference/mcp/get-post.md) | | "What have we posted this week?" | [`list_posts`](https://docs.flung.dev/reference/mcp/list-posts.md) | | "Show scheduled posts." | [`list_posts`](https://docs.flung.dev/reference/mcp/list-posts.md) | | "How did the launch post do?" | [`get_post_metrics`](https://docs.flung.dev/reference/mcp/get-post-metrics.md) | ## Planning | Say | The agent calls | |---|---| | "How long can a Bluesky post be?" | [`list_platforms`](https://docs.flung.dev/reference/mcp/list-platforms.md) | | "Which platforms take video?" | [`list_platforms`](https://docs.flung.dev/reference/mcp/list-platforms.md) | | "When should we post this on LinkedIn?" | [`suggest_posting_times`](https://docs.flung.dev/reference/mcp/suggest-posting-times.md) | ## Inbox | Say | The agent calls | |---|---| | "Any new comments?" | [`list_inbox`](https://docs.flung.dev/reference/mcp/list-inbox.md) | | "What did people say about the launch post?" | [`list_inbox`](https://docs.flung.dev/reference/mcp/list-inbox.md) | | "Thank Bob for his comment." | [`reply_to_inbox_item`](https://docs.flung.dev/reference/mcp/reply-to-inbox-item.md) | ## Automation | Say | The agent calls | |---|---| | "Which templates do we have?" | [`list_templates`](https://docs.flung.dev/reference/mcp/list-templates.md) | | "Post the weekly news template on Friday." | [`deploy_template`](https://docs.flung.dev/reference/mcp/deploy-template.md) | | "What variables are set?" | [`list_variables`](https://docs.flung.dev/reference/mcp/list-variables.md) | | "Set our hashtag variable to #flung." | [`set_variable`](https://docs.flung.dev/reference/mcp/set-variable.md) | | "Is the blog feed still posting?" | [`list_feeds`](https://docs.flung.dev/reference/mcp/list-feeds.md) | | "Post our blog's new articles to LinkedIn." | [`follow_feed`](https://docs.flung.dev/reference/mcp/follow-feed.md) | ## Workspace | Say | The agent calls | |---|---| | "What level are we?" | [`get_experience`](https://docs.flung.dev/reference/mcp/get-experience.md) | | "How close are we to the next achievement?" | [`get_experience`](https://docs.flung.dev/reference/mcp/get-experience.md) | --- # Files for agents > Page: https://docs.flung.dev/agents/machine-readable. Index of every page: https://docs.flung.dev/llms.txt Every file this site serves for programs and AI agents, and when to fetch each. ## TL;DR | Question | Answer | |---|---| | Where do I start? | `/llms.txt`: every page with a one-line summary and its size. | | Can I read one page as Markdown? | Yes: add `.md` to its address. | ## Files | File | What it holds | Fetch it when | |---|---|---| | `/llms.txt` | The index of every page | You need to find the right page | | `/llms-full.txt` | Every page in one file | You want all of it in one read | | `/llms-docs.txt` | The guides | You are building or posting | | `/llms-reference.txt` | The API, SDK and tool references | You need exact fields | | `/llms-agents.txt` | The pages for agents | You are an agent learning the rules | | `/openapi.json` | The API, OpenAPI `3.1`, 48 paths | You generate a client or check a field | | `/tools.json` | The 28 MCP tools with JSON Schemas | You call the tools without MCP discovery | | `/sitemap.xml` | Every page address | You crawl |