# 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<Response> {
  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 |
