Skip to content
Connect an agent

Overview

TypeScript SDK

@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

QuestionAnswer
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

OptionTypeRequiredDescription
apiKeystringyesThe 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.
baseUrlstringnoWhere the API lives. Defaults to production; pass https://api-preview.flung.dev to test against preview.
maxRetriesnumbernoAttempts after the first for network failures, timeouts, 429 and 5xx answers. Default 2.
timeoutMsnumbernoHow long one attempt may take before it is abandoned (and retried). Default 60 seconds.
fetchtypeof fetchnoA 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.

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

FieldTypeRequiredDescription
bodystringyesThe raw request body, exactly as received. Parsing and re-serialising it changes the bytes and breaks the signature.
headersHeaderSourceyesThe request's headers, as a Fetch Headers or Node's req.headers; any casing works.
secretstring | readonly string[]yesThe endpoint's whsec_ secret. Pass two while you switch to a rotated secret; either one is accepted.
nowSecondsnumbernoThe current time in Unix seconds, for tests.

Every method

flung.posts

MethodEndpointWhat it does
flung.posts.list()GET /v1/postsList posts
flung.posts.create()POST /v1/postsSchedule a post
flung.posts.get()GET /v1/posts/{id}Get a post
flung.posts.update()PATCH /v1/posts/{id}Change a scheduled post
flung.posts.delete()DELETE /v1/posts/{id}Delete a post
flung.posts.cancel()POST /v1/posts/{id}/cancelCancel a scheduled post
flung.posts.retry()POST /v1/posts/{id}/retryRetry the failed accounts of a post
flung.posts.metrics()GET /v1/posts/{id}/metricsRead a post's counts

flung.accounts

MethodEndpointWhat it does
flung.accounts.list()GET /v1/accountsList connected accounts
flung.accounts.get()GET /v1/accounts/{id}Get a connected account
flung.accounts.update()PATCH /v1/accounts/{id}Change an account's settings
flung.accounts.disconnect()DELETE /v1/accounts/{id}Disconnect an account
flung.accounts.refresh()POST /v1/accounts/{id}/refreshRefresh an account now
flung.accounts.boards()GET /v1/accounts/{id}/boardsList Pinterest boards
flung.accounts.postingOptions()GET /v1/accounts/{id}/posting-optionsRead what a post may choose
flung.accounts.connect()POST /v1/connect/{platform}Start connecting an account
flung.accounts.reconnect()POST /v1/accounts/{id}/reconnectStart reconnecting an account
flung.accounts.syncInbox()POST /v1/accounts/{id}/inbox/syncRead an account's inbox now

flung.webhookEndpoints

MethodEndpointWhat it does
flung.webhookEndpoints.list()GET /v1/webhook-endpointsList webhook endpoints
flung.webhookEndpoints.create()POST /v1/webhook-endpointsAdd a webhook endpoint
flung.webhookEndpoints.rotateSecret()POST /v1/webhook-endpoints/{id}/rotate-secretRotate a signing secret
flung.webhookEndpoints.update()PATCH /v1/webhook-endpoints/{id}Change an endpoint
flung.webhookEndpoints.delete()DELETE /v1/webhook-endpoints/{id}Delete a webhook endpoint
flung.webhookEndpoints.ping()POST /v1/webhook-endpoints/{id}/pingSend a test event
flung.webhookEndpoints.deliveries()GET /v1/webhook-endpoints/{id}/deliveriesList deliveries

flung.connectTokens

MethodEndpointWhat it does
flung.connectTokens.create()POST /v1/connect-tokensMake a connect token for a page

flung.connectSessions

MethodEndpointWhat it does
flung.connectSessions.get()GET /v1/connect-sessions/{id}Get a connect session

flung.apiKeys

MethodEndpointWhat it does
flung.apiKeys.list()GET /v1/api-keysList API keys
flung.apiKeys.create()POST /v1/api-keysCreate an API key
flung.apiKeys.revoke()DELETE /v1/api-keys/{id}Revoke an API key

flung.me

MethodEndpointWhat it does
flung.me.get()GET /v1/meWho is calling

flung.members

MethodEndpointWhat it does
flung.members.list()GET /v1/membersList members
flung.members.add()POST /v1/membersAdd a member
flung.members.remove()DELETE /v1/members/{userId}Remove a member

flung.media

MethodEndpointWhat it does
flung.media.list()GET /v1/mediaList files
flung.media.create()POST /v1/mediaStart an upload
flung.media.uploadPart()PUT /v1/media/{id}/parts/{partNumber}Upload one part
flung.media.complete()POST /v1/media/{id}/completeFinish a multipart upload
flung.media.upload()PUT /v1/media/{id}/contentUpload the bytes
flung.media.import()POST /v1/media/importImport a file from a URL
flung.media.get()GET /v1/media/{id}Get a file
flung.media.delete()DELETE /v1/media/{id}Delete a file

flung.templates

MethodEndpointWhat it does
flung.templates.list()GET /v1/templatesList templates
flung.templates.create()POST /v1/templatesCreate a template
flung.templates.get()GET /v1/templates/{id}Get a template
flung.templates.update()PATCH /v1/templates/{id}Change a template
flung.templates.delete()DELETE /v1/templates/{id}Delete a template
flung.templates.deploy()POST /v1/templates/{id}/deploySchedule a post from a template

flung.variables

MethodEndpointWhat it does
flung.variables.list()GET /v1/variablesList variables
flung.variables.set()PUT /v1/variables/{name}Set a variable
flung.variables.delete()DELETE /v1/variables/{name}Delete a variable

flung.feeds

MethodEndpointWhat it does
flung.feeds.list()GET /v1/feedsList feeds
flung.feeds.create()POST /v1/feedsFollow an RSS or Atom feed
flung.feeds.update()PATCH /v1/feeds/{id}Change a feed
flung.feeds.delete()DELETE /v1/feeds/{id}Stop following a feed

flung.platforms

MethodEndpointWhat it does
flung.platforms.list()GET /v1/platformsList platforms and their limits
flung.platforms.postingTimes()GET /v1/posting-timesSuggest when to post

flung.analytics

MethodEndpointWhat it does
flung.analytics.usage()GET /v1/usageAPI requests per credential per day
flung.analytics.stats()GET /v1/statsWorkspace statistics

flung.inbox

MethodEndpointWhat it does
flung.inbox.list()GET /v1/inboxList comments and mentions
flung.inbox.get()GET /v1/inbox/{id}Get an inbox item
flung.inbox.update()PATCH /v1/inbox/{id}Mark an item read, unread or archived
flung.inbox.reply()POST /v1/inbox/{id}/replyReply publicly

flung.experience

MethodEndpointWhat it does
flung.experience.get()GET /v1/experienceRead XP, level and achievements
flung.experience.leaderboard()GET /v1/leaderboardRead the leaderboard
flung.experience.joinLeaderboard()PUT /v1/leaderboard/entryJoin the leaderboard
flung.experience.leaveLeaderboard()DELETE /v1/leaderboard/entryLeave the leaderboard

See also

DocWhy
Connect panelLet people connect accounts from your page
API referenceEvery endpoint, with samples
WebhooksReceiving and verifying events