# Schemas

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

Every named request and response type of the API, with its fields. Each endpoint page links the types it uses.

## TL;DR

| Question | Answer |
|---|---|
| What is a schema? | The shape of a JSON body: its fields, their types, and which are required. |
| Where is the one an endpoint uses? | On the endpoint's page, whose request and response types link here. |
| How many are there? | 51. |

## Post

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `status` | `"scheduled" \| "publishing" \| "published" \| "partially_published" \| "failed" \| "cancelled"` | yes |  |
| `content` | `string` | yes |  |
| `mediaIds` | `string[]` | yes |  |
| `platformOverrides` | `object` | yes |  |
| `platformOverrides.x` | `object` | no |  |
| `platformOverrides.x.content` | `string` | no |  |
| `platformOverrides.instagram` | `object` | no |  |
| `platformOverrides.instagram.content` | `string` | no |  |
| `platformOverrides.facebook` | `object` | no |  |
| `platformOverrides.facebook.content` | `string` | no |  |
| `platformOverrides.threads` | `object` | no |  |
| `platformOverrides.threads.content` | `string` | no |  |
| `platformOverrides.linkedin` | `object` | no |  |
| `platformOverrides.linkedin.content` | `string` | no |  |
| `platformOverrides.tiktok` | `object` | no |  |
| `platformOverrides.tiktok.content` | `string` | no |  |
| `platformOverrides.youtube` | `object` | no |  |
| `platformOverrides.youtube.content` | `string` | no |  |
| `platformOverrides.pinterest` | `object` | no |  |
| `platformOverrides.pinterest.content` | `string` | no |  |
| `platformOverrides.bluesky` | `object` | no |  |
| `platformOverrides.bluesky.content` | `string` | no |  |
| `platformOverrides.mastodon` | `object` | no |  |
| `platformOverrides.mastodon.content` | `string` | no |  |
| `platformOverrides.discord` | `object` | no |  |
| `platformOverrides.discord.content` | `string` | no |  |
| `platformOverrides.farcaster` | `object` | no |  |
| `platformOverrides.farcaster.content` | `string` | no |  |
| `publishAt` | `string` | yes |  |
| `createdAt` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |
| `targets` | `PostTarget[]` | yes |  |
| `targets[].id` | `string` | yes |  |
| `targets[].accountId` | `string` | yes |  |
| `targets[].platform` | `Platform` | yes |  |
| `targets[].status` | `"pending" \| "publishing" \| "published" \| "failed" \| "cancelled"` | yes |  |
| `targets[].platformPostId` | `string \| null` | yes |  |
| `targets[].url` | `string \| null` | yes |  |
| `targets[].error` | `object \| null` | yes |  |
| `targets[].error.code` | `string` | yes |  |
| `targets[].error.message` | `string` | yes |  |
| `targets[].publishedAt` | `string \| null` | yes |  |

## PostTarget

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `accountId` | `string` | yes |  |
| `platform` | `Platform` | yes |  |
| `status` | `"pending" \| "publishing" \| "published" \| "failed" \| "cancelled"` | yes |  |
| `platformPostId` | `string \| null` | yes |  |
| `url` | `string \| null` | yes |  |
| `error` | `object \| null` | yes |  |
| `error.code` | `string` | yes |  |
| `error.message` | `string` | yes |  |
| `publishedAt` | `string \| null` | yes |  |

## Platform

`"x" \| "instagram" \| "facebook" \| "threads" \| "linkedin" \| "tiktok" \| "youtube" \| "pinterest" \| "bluesky" \| "mastodon" \| "discord" \| "farcaster"`

## Error

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | `object` | yes |  |
| `error.code` | `"unauthorized" \| "forbidden" \| "validation_failed" \| "not_found" \| "unknown_accounts" \| "platform_not_supported" \| "media_required" \| "account_needs_reconnect" \| "not_configured" \| "connect_failed" \| "platform_error" \| "post_not_editable" \| "unknown_variable" \| "rate_limited" \| "unsupported_media" \| "media_too_large" \| "unknown_media" \| "media_not_ready" \| "text_too_long" \| "publish_at_in_past" \| "idempotency_key_reused" \| "internal_error"` | yes |  |
| `error.message` | `string` | yes |  |
| `error.details` | `any` | no |  |

## PostList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `PostSummary[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].status` | `"scheduled" \| "publishing" \| "published" \| "partially_published" \| "failed" \| "cancelled"` | yes |  |
| `data[].content` | `string` | yes |  |
| `data[].mediaIds` | `string[]` | yes |  |
| `data[].platformOverrides` | `object` | yes |  |
| `data[].platformOverrides.x` | `object` | no |  |
| `data[].platformOverrides.instagram` | `object` | no |  |
| `data[].platformOverrides.facebook` | `object` | no |  |
| `data[].platformOverrides.threads` | `object` | no |  |
| `data[].platformOverrides.linkedin` | `object` | no |  |
| `data[].platformOverrides.tiktok` | `object` | no |  |
| `data[].platformOverrides.youtube` | `object` | no |  |
| `data[].platformOverrides.pinterest` | `object` | no |  |
| `data[].platformOverrides.bluesky` | `object` | no |  |
| `data[].platformOverrides.mastodon` | `object` | no |  |
| `data[].platformOverrides.discord` | `object` | no |  |
| `data[].platformOverrides.farcaster` | `object` | no |  |
| `data[].publishAt` | `string` | yes |  |
| `data[].createdAt` | `string` | yes |  |
| `data[].updatedAt` | `string` | yes |  |
| `hasMore` | `boolean` | yes |  |

## PostSummary

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `status` | `"scheduled" \| "publishing" \| "published" \| "partially_published" \| "failed" \| "cancelled"` | yes |  |
| `content` | `string` | yes |  |
| `mediaIds` | `string[]` | yes |  |
| `platformOverrides` | `object` | yes |  |
| `platformOverrides.x` | `object` | no |  |
| `platformOverrides.x.content` | `string` | no |  |
| `platformOverrides.instagram` | `object` | no |  |
| `platformOverrides.instagram.content` | `string` | no |  |
| `platformOverrides.facebook` | `object` | no |  |
| `platformOverrides.facebook.content` | `string` | no |  |
| `platformOverrides.threads` | `object` | no |  |
| `platformOverrides.threads.content` | `string` | no |  |
| `platformOverrides.linkedin` | `object` | no |  |
| `platformOverrides.linkedin.content` | `string` | no |  |
| `platformOverrides.tiktok` | `object` | no |  |
| `platformOverrides.tiktok.content` | `string` | no |  |
| `platformOverrides.youtube` | `object` | no |  |
| `platformOverrides.youtube.content` | `string` | no |  |
| `platformOverrides.pinterest` | `object` | no |  |
| `platformOverrides.pinterest.content` | `string` | no |  |
| `platformOverrides.bluesky` | `object` | no |  |
| `platformOverrides.bluesky.content` | `string` | no |  |
| `platformOverrides.mastodon` | `object` | no |  |
| `platformOverrides.mastodon.content` | `string` | no |  |
| `platformOverrides.discord` | `object` | no |  |
| `platformOverrides.discord.content` | `string` | no |  |
| `platformOverrides.farcaster` | `object` | no |  |
| `platformOverrides.farcaster.content` | `string` | no |  |
| `publishAt` | `string` | yes |  |
| `createdAt` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |

## AccountList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Account[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].platform` | `Platform` | yes |  |
| `data[].platformUserId` | `string` | yes |  |
| `data[].username` | `string \| null` | yes |  |
| `data[].profileId` | `string \| null` | yes |  |
| `data[].status` | `"active" \| "needs_reconnect" \| "disconnected"` | yes | `needs_reconnect`: the platform refused the token; start a reconnect. |
| `data[].renewal` | `"automatic" \| "manual" \| "not_needed"` | yes | `automatic`: Flung refreshes the token before it expires, until `reconnectBy` if set. `manual`: only a reconnect renews it, by `reconnectBy`. `not_needed`: the token does not expire. |
| `data[].tokenExpiresAt` | `string \| null` | yes | When the current access token expires. Flung renews `automatic` ones about a day before. |
| `data[].reconnectBy` | `string \| null` | yes | The last moment the account works without a reconnect; null when it renews indefinitely. Flung sends `account.reconnect_required_soon` a week before. |
| `data[].defaultBoard` | `object \| null` | yes | Pinterest only: where pins go unless a post picks another board. |
| `data[].defaultBoard.id` | `string` | yes |  |
| `data[].defaultBoard.name` | `string \| null` | yes |  |
| `data[].inbox` | `object` | yes | Reading comments and mentions into GET /v1/inbox. |
| `data[].inbox.enabled` | `boolean \| null` | yes | Whether comments and mentions are read. null is the platform's default: on, except X, where reading costs money. |
| `data[].inbox.readAt` | `string \| null` | yes |  |
| `data[].inbox.error` | `string \| null` | yes | Why the last read failed, such as a missing permission. |
| `data[].createdAt` | `string` | yes |  |
| `data[].updatedAt` | `string` | yes |  |

## Account

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `platform` | `Platform` | yes |  |
| `platformUserId` | `string` | yes |  |
| `username` | `string \| null` | yes |  |
| `profileId` | `string \| null` | yes |  |
| `status` | `"active" \| "needs_reconnect" \| "disconnected"` | yes | `needs_reconnect`: the platform refused the token; start a reconnect. |
| `renewal` | `"automatic" \| "manual" \| "not_needed"` | yes | `automatic`: Flung refreshes the token before it expires, until `reconnectBy` if set. `manual`: only a reconnect renews it, by `reconnectBy`. `not_needed`: the token does not expire. |
| `tokenExpiresAt` | `string \| null` | yes | When the current access token expires. Flung renews `automatic` ones about a day before. |
| `reconnectBy` | `string \| null` | yes | The last moment the account works without a reconnect; null when it renews indefinitely. Flung sends `account.reconnect_required_soon` a week before. |
| `defaultBoard` | `object \| null` | yes | Pinterest only: where pins go unless a post picks another board. |
| `defaultBoard.id` | `string` | yes |  |
| `defaultBoard.name` | `string \| null` | yes |  |
| `inbox` | `object` | yes | Reading comments and mentions into GET /v1/inbox. |
| `inbox.enabled` | `boolean \| null` | yes | Whether comments and mentions are read. null is the platform's default: on, except X, where reading costs money. |
| `inbox.readAt` | `string \| null` | yes |  |
| `inbox.error` | `string \| null` | yes | Why the last read failed, such as a missing permission. |
| `createdAt` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |

## RefreshResult

| Field | Type | Required | Description |
|---|---|---|---|
| `result` | `"refreshed" \| "not_needed" \| "reconnect_required" \| "failed"` | yes | `refreshed`: a new token is stored. `not_needed`: this account has nothing to refresh. `reconnect_required`: the platform refused; the account is now `needs_reconnect`. `failed`: a temporary platform failure; try again later. |
| `account` | `Account` | yes |  |
| `account.id` | `string` | yes |  |
| `account.platform` | `Platform` | yes |  |
| `account.platformUserId` | `string` | yes |  |
| `account.username` | `string \| null` | yes |  |
| `account.profileId` | `string \| null` | yes |  |
| `account.status` | `"active" \| "needs_reconnect" \| "disconnected"` | yes | `needs_reconnect`: the platform refused the token; start a reconnect. |
| `account.renewal` | `"automatic" \| "manual" \| "not_needed"` | yes | `automatic`: Flung refreshes the token before it expires, until `reconnectBy` if set. `manual`: only a reconnect renews it, by `reconnectBy`. `not_needed`: the token does not expire. |
| `account.tokenExpiresAt` | `string \| null` | yes | When the current access token expires. Flung renews `automatic` ones about a day before. |
| `account.reconnectBy` | `string \| null` | yes | The last moment the account works without a reconnect; null when it renews indefinitely. Flung sends `account.reconnect_required_soon` a week before. |
| `account.defaultBoard` | `object \| null` | yes | Pinterest only: where pins go unless a post picks another board. |
| `account.defaultBoard.id` | `string` | yes |  |
| `account.defaultBoard.name` | `string \| null` | yes |  |
| `account.inbox` | `object` | yes | Reading comments and mentions into GET /v1/inbox. |
| `account.inbox.enabled` | `boolean \| null` | yes | Whether comments and mentions are read. null is the platform's default: on, except X, where reading costs money. |
| `account.inbox.readAt` | `string \| null` | yes |  |
| `account.inbox.error` | `string \| null` | yes | Why the last read failed, such as a missing permission. |
| `account.createdAt` | `string` | yes |  |
| `account.updatedAt` | `string` | yes |  |

## BoardList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Board[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].name` | `string` | yes |  |

## Board

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `name` | `string` | yes |  |

## PostingOptions

| Field | Type | Required | Description |
|---|---|---|---|
| `creatorName` | `string \| null` | yes |  |
| `privacyLevels` | `string[]` | yes | The audiences this account may post to now; pass one as platformOverrides.tiktok.privacyLevel. |
| `commentsDisabled` | `boolean` | yes | The creator turned comments off; posts cannot allow them. |
| `duetDisabled` | `boolean` | yes |  |
| `stitchDisabled` | `boolean` | yes |  |
| `maxVideoSeconds` | `integer \| null` | yes |  |

## CreatedWebhookEndpoint

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `url` | `string` | yes |  |
| `events` | `string[]` | yes |  |
| `enabled` | `boolean` | yes |  |
| `createdAt` | `string` | yes |  |
| `secret` | `string` | yes | The signing secret (Standard Webhooks, `whsec_...`). Shown only in this response; store it now. |

## WebhookEvent

`"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"`

## WebhookEndpointList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `WebhookEndpoint[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].url` | `string` | yes |  |
| `data[].events` | `string[]` | yes |  |
| `data[].enabled` | `boolean` | yes |  |
| `data[].createdAt` | `string` | yes |  |

## WebhookEndpoint

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `url` | `string` | yes |  |
| `events` | `string[]` | yes |  |
| `enabled` | `boolean` | yes |  |
| `createdAt` | `string` | yes |  |

## RotatedSecret

| Field | Type | Required | Description |
|---|---|---|---|
| `secret` | `string` | yes | The new signing secret. Shown only in this response. |
| `previousSecretExpiresAt` | `string` | yes | Until then, deliveries carry signatures from both secrets. |

## PingSent

| Field | Type | Required | Description |
|---|---|---|---|
| `eventId` | `string` | yes |  |

## WebhookDeliveryList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `WebhookDelivery[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].eventId` | `string` | yes |  |
| `data[].eventType` | `string` | yes | An event type, or `webhook.ping` for a test. |
| `data[].status` | `"pending" \| "delivered" \| "failed"` | yes | `pending` while retries remain. |
| `data[].attempts` | `integer` | yes |  |
| `data[].responseStatus` | `integer \| null` | yes |  |
| `data[].lastError` | `string \| null` | yes |  |
| `data[].createdAt` | `string` | yes |  |
| `data[].updatedAt` | `string` | yes |  |
| `hasMore` | `boolean` | yes |  |

## WebhookDelivery

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `eventId` | `string` | yes |  |
| `eventType` | `string` | yes | An event type, or `webhook.ping` for a test. |
| `status` | `"pending" \| "delivered" \| "failed"` | yes | `pending` while retries remain. |
| `attempts` | `integer` | yes |  |
| `responseStatus` | `integer \| null` | yes |  |
| `lastError` | `string \| null` | yes |  |
| `createdAt` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |

## ConnectToken

| Field | Type | Required | Description |
|---|---|---|---|
| `token` | `string` | yes | Hand this to the page (`<flung-connect token>`). It can only list, connect, reconnect and disconnect accounts. |
| `expiresAt` | `string` | yes |  |
| `origins` | `string[]` | yes |  |
| `profileId` | `string \| null` | yes |  |
| `platforms` | `array \| null` | yes |  |

## ConnectSession

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `platform` | `Platform` | yes |  |
| `accountId` | `string \| null` | yes | The account being reconnected; null for a new connection. |
| `status` | `"pending" \| "connected" \| "failed" \| "expired"` | yes | `pending` until the person finishes on the platform; `expired` when they did not by `expiresAt`. |
| `authorizationUrl` | `string \| null` | yes | Send the person here: the platform's own consent page. Null once the session has ended. |
| `expiresAt` | `string` | yes | The person must finish by then. |
| `accountIds` | `string[]` | yes | The accounts stored, once `connected`. Facebook connects one per page. |
| `error` | `object \| null` | yes | Why it `failed`: `access_denied`, `wrong_account`, `platform_error` and so on. |
| `error.code` | `string` | yes |  |
| `error.message` | `string` | yes |  |
| `createdAt` | `string` | yes |  |
| `completedAt` | `string \| null` | yes |  |

## CreatedApiKey

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `name` | `string` | yes |  |
| `prefix` | `string` | yes | The start of the key, to tell keys apart. |
| `scopes` | `string[]` | yes |  |
| `lastUsedAt` | `string \| null` | yes |  |
| `expiresAt` | `string \| null` | yes |  |
| `revokedAt` | `string \| null` | yes |  |
| `createdAt` | `string` | yes |  |
| `key` | `string` | yes | The secret key. Shown only in this response; store it now. |

## ApiKeyList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `ApiKey[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].name` | `string` | yes |  |
| `data[].prefix` | `string` | yes | The start of the key, to tell keys apart. |
| `data[].scopes` | `string[]` | yes |  |
| `data[].lastUsedAt` | `string \| null` | yes |  |
| `data[].expiresAt` | `string \| null` | yes |  |
| `data[].revokedAt` | `string \| null` | yes |  |
| `data[].createdAt` | `string` | yes |  |

## ApiKey

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `name` | `string` | yes |  |
| `prefix` | `string` | yes | The start of the key, to tell keys apart. |
| `scopes` | `string[]` | yes |  |
| `lastUsedAt` | `string \| null` | yes |  |
| `expiresAt` | `string \| null` | yes |  |
| `revokedAt` | `string \| null` | yes |  |
| `createdAt` | `string` | yes |  |

## Me

| Field | Type | Required | Description |
|---|---|---|---|
| `workspaceId` | `string` | yes |  |
| `via` | `"api_key" \| "oauth"` | yes | How this request authenticated. |
| `userId` | `string \| null` | yes | The person who approved the app, for an OAuth token; null for an API key. |
| `email` | `string \| null` | yes |  |

## MemberList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Member[]` | yes |  |
| `data[].userId` | `string` | yes |  |
| `data[].email` | `string` | yes |  |
| `data[].role` | `"owner" \| "member"` | yes | `owner` may add and remove members; both may connect apps to the workspace. |
| `data[].createdAt` | `string` | yes |  |

## Member

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | `string` | yes |  |
| `email` | `string` | yes |  |
| `role` | `"owner" \| "member"` | yes | `owner` may add and remove members; both may connect apps to the workspace. |
| `createdAt` | `string` | yes |  |

## MediaUpload

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `contentType` | `MediaType` | yes | What the file really is, read from its first bytes once uploaded. |
| `filename` | `string \| null` | yes |  |
| `bytes` | `integer \| null` | yes |  |
| `status` | `"pending" \| "processing" \| "ready" \| "failed"` | yes | `pending` until its bytes arrive; `ready` once a post can use it. |
| `preview` | `object \| null` | yes | Videos only, where previews are set up: a thumbnail. Read the file again while `processing`. |
| `preview.status` | `"processing" \| "ready" \| "failed" \| "skipped"` | yes | `skipped` when the video is over 100 MB or this month's thumbnail allowance is used up. Posts never wait for a preview; they use the file as uploaded. |
| `preview.thumbnailUrl` | `string \| null` | yes | A JPEG frame one second in, 640 pixels wide, at an address that does not expire. |
| `preview.hlsUrl` | `string \| null` | yes | Always null for now: streaming versions are not made. |
| `preview.dashUrl` | `string \| null` | yes | Always null for now. |
| `preview.durationSeconds` | `number \| null` | yes | Always null for now. |
| `createdAt` | `string` | yes |  |
| `uploadUrl` | `string \| null` | yes | For a file up to 100 MB: PUT it here, with your credential and its Content-Length. Null for a multipart upload. |
| `multipart` | `object \| null` | yes | For a file over 100 MB: how to send it in parts. |
| `multipart.partSize` | `integer` | yes | Every part but the last is exactly this many bytes. |
| `multipart.partCount` | `integer` | yes |  |
| `multipart.partUrl` | `string` | yes | PUT each part to this address with `{partNumber}` replaced by 1 to partCount, in any order. |
| `multipart.completeUrl` | `string` | yes | POST here once every part is in. |

## MediaType

What the file really is, read from its first bytes once uploaded.

`"image/jpeg" \| "image/png" \| "image/webp" \| "image/gif" \| "video/mp4" \| "video/quicktime"`

## Media

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `contentType` | `MediaType` | yes | What the file really is, read from its first bytes once uploaded. |
| `filename` | `string \| null` | yes |  |
| `bytes` | `integer \| null` | yes |  |
| `status` | `"pending" \| "processing" \| "ready" \| "failed"` | yes | `pending` until its bytes arrive; `ready` once a post can use it. |
| `preview` | `object \| null` | yes | Videos only, where previews are set up: a thumbnail. Read the file again while `processing`. |
| `preview.status` | `"processing" \| "ready" \| "failed" \| "skipped"` | yes | `skipped` when the video is over 100 MB or this month's thumbnail allowance is used up. Posts never wait for a preview; they use the file as uploaded. |
| `preview.thumbnailUrl` | `string \| null` | yes | A JPEG frame one second in, 640 pixels wide, at an address that does not expire. |
| `preview.hlsUrl` | `string \| null` | yes | Always null for now: streaming versions are not made. |
| `preview.dashUrl` | `string \| null` | yes | Always null for now. |
| `preview.durationSeconds` | `number \| null` | yes | Always null for now. |
| `createdAt` | `string` | yes |  |

## UploadedPart

| Field | Type | Required | Description |
|---|---|---|---|
| `partNumber` | `integer` | yes |  |
| `bytes` | `integer` | yes |  |

## MediaList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Media[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].contentType` | `MediaType` | yes | What the file really is, read from its first bytes once uploaded. |
| `data[].filename` | `string \| null` | yes |  |
| `data[].bytes` | `integer \| null` | yes |  |
| `data[].status` | `"pending" \| "processing" \| "ready" \| "failed"` | yes | `pending` until its bytes arrive; `ready` once a post can use it. |
| `data[].preview` | `object \| null` | yes | Videos only, where previews are set up: a thumbnail. Read the file again while `processing`. |
| `data[].preview.status` | `"processing" \| "ready" \| "failed" \| "skipped"` | yes | `skipped` when the video is over 100 MB or this month's thumbnail allowance is used up. Posts never wait for a preview; they use the file as uploaded. |
| `data[].preview.thumbnailUrl` | `string \| null` | yes | A JPEG frame one second in, 640 pixels wide, at an address that does not expire. |
| `data[].preview.hlsUrl` | `string \| null` | yes | Always null for now: streaming versions are not made. |
| `data[].preview.dashUrl` | `string \| null` | yes | Always null for now. |
| `data[].preview.durationSeconds` | `number \| null` | yes | Always null for now. |
| `data[].createdAt` | `string` | yes |  |
| `hasMore` | `boolean` | yes |  |

## Template

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `name` | `string` | yes |  |
| `content` | `string` | yes |  |
| `mediaIds` | `string[]` | yes |  |
| `accountIds` | `string[]` | yes |  |
| `platformOverrides` | `object` | yes |  |
| `platformOverrides.x` | `object` | no |  |
| `platformOverrides.x.content` | `string` | no |  |
| `platformOverrides.x.boardId` | `string` | no |  |
| `platformOverrides.instagram` | `object` | no |  |
| `platformOverrides.instagram.content` | `string` | no |  |
| `platformOverrides.instagram.boardId` | `string` | no |  |
| `platformOverrides.facebook` | `object` | no |  |
| `platformOverrides.facebook.content` | `string` | no |  |
| `platformOverrides.facebook.boardId` | `string` | no |  |
| `platformOverrides.threads` | `object` | no |  |
| `platformOverrides.threads.content` | `string` | no |  |
| `platformOverrides.threads.boardId` | `string` | no |  |
| `platformOverrides.linkedin` | `object` | no |  |
| `platformOverrides.linkedin.content` | `string` | no |  |
| `platformOverrides.linkedin.boardId` | `string` | no |  |
| `platformOverrides.tiktok` | `object` | no |  |
| `platformOverrides.tiktok.content` | `string` | no |  |
| `platformOverrides.tiktok.boardId` | `string` | no |  |
| `platformOverrides.youtube` | `object` | no |  |
| `platformOverrides.youtube.content` | `string` | no |  |
| `platformOverrides.youtube.boardId` | `string` | no |  |
| `platformOverrides.pinterest` | `object` | no |  |
| `platformOverrides.pinterest.content` | `string` | no |  |
| `platformOverrides.pinterest.boardId` | `string` | no |  |
| `platformOverrides.bluesky` | `object` | no |  |
| `platformOverrides.bluesky.content` | `string` | no |  |
| `platformOverrides.bluesky.boardId` | `string` | no |  |
| `platformOverrides.mastodon` | `object` | no |  |
| `platformOverrides.mastodon.content` | `string` | no |  |
| `platformOverrides.mastodon.boardId` | `string` | no |  |
| `platformOverrides.discord` | `object` | no |  |
| `platformOverrides.discord.content` | `string` | no |  |
| `platformOverrides.discord.boardId` | `string` | no |  |
| `platformOverrides.farcaster` | `object` | no |  |
| `platformOverrides.farcaster.content` | `string` | no |  |
| `platformOverrides.farcaster.boardId` | `string` | no |  |
| `createdAt` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |

## TemplateList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Template[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].name` | `string` | yes |  |
| `data[].content` | `string` | yes |  |
| `data[].mediaIds` | `string[]` | yes |  |
| `data[].accountIds` | `string[]` | yes |  |
| `data[].platformOverrides` | `object` | yes |  |
| `data[].platformOverrides.x` | `object` | no |  |
| `data[].platformOverrides.instagram` | `object` | no |  |
| `data[].platformOverrides.facebook` | `object` | no |  |
| `data[].platformOverrides.threads` | `object` | no |  |
| `data[].platformOverrides.linkedin` | `object` | no |  |
| `data[].platformOverrides.tiktok` | `object` | no |  |
| `data[].platformOverrides.youtube` | `object` | no |  |
| `data[].platformOverrides.pinterest` | `object` | no |  |
| `data[].platformOverrides.bluesky` | `object` | no |  |
| `data[].platformOverrides.mastodon` | `object` | no |  |
| `data[].platformOverrides.discord` | `object` | no |  |
| `data[].platformOverrides.farcaster` | `object` | no |  |
| `data[].createdAt` | `string` | yes |  |
| `data[].updatedAt` | `string` | yes |  |

## VariableList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Variable[]` | yes |  |
| `data[].name` | `string` | yes |  |
| `data[].value` | `string` | yes |  |
| `data[].updatedAt` | `string` | yes |  |

## Variable

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `value` | `string` | yes |  |
| `updatedAt` | `string` | yes |  |

## Feed

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `url` | `string` | yes |  |
| `accountIds` | `string[]` | yes |  |
| `template` | `string` | yes | The post text: `{{title}}`, `{{link}}`, `{{summary}}` from each item, and workspace variables. |
| `enabled` | `boolean` | yes |  |
| `lastCheckedAt` | `string \| null` | yes |  |
| `lastError` | `string \| null` | yes |  |
| `createdAt` | `string` | yes |  |

## FeedList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `Feed[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].url` | `string` | yes |  |
| `data[].accountIds` | `string[]` | yes |  |
| `data[].template` | `string` | yes | The post text: `{{title}}`, `{{link}}`, `{{summary}}` from each item, and workspace variables. |
| `data[].enabled` | `boolean` | yes |  |
| `data[].lastCheckedAt` | `string \| null` | yes |  |
| `data[].lastError` | `string \| null` | yes |  |
| `data[].createdAt` | `string` | yes |  |

## PlatformList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `PlatformInfo[]` | yes |  |
| `data[].platform` | `Platform` | yes |  |
| `data[].connectable` | `boolean` | yes | Whether this Flung has the platform's app credentials, so accounts can be connected. |
| `data[].maxTextLength` | `integer` | yes |  |
| `data[].requiresMedia` | `boolean` | yes | Refuses text-only posts. |
| `data[].maxImages` | `integer` | yes | Images in one post; 0 when images are not accepted. |
| `data[].video` | `boolean` | yes | Takes one video per post. |
| `data[].maxImageBytes` | `integer` | yes |  |
| `data[].maxVideoBytes` | `integer` | yes |  |
| `data[].postsPerHour` | `integer` | yes | How fast Flung publishes to one account, to stay inside the platform's limits. |
| `data[].tokensExpire` | `boolean` | yes | Whether connections expire; Flung renews them before they do where the platform allows. |
| `data[].destinations` | `boolean` | yes | Posts go to a destination chosen per post (Pinterest boards). |

## PlatformInfo

| Field | Type | Required | Description |
|---|---|---|---|
| `platform` | `Platform` | yes |  |
| `connectable` | `boolean` | yes | Whether this Flung has the platform's app credentials, so accounts can be connected. |
| `maxTextLength` | `integer` | yes |  |
| `requiresMedia` | `boolean` | yes | Refuses text-only posts. |
| `maxImages` | `integer` | yes | Images in one post; 0 when images are not accepted. |
| `video` | `boolean` | yes | Takes one video per post. |
| `maxImageBytes` | `integer` | yes |  |
| `maxVideoBytes` | `integer` | yes |  |
| `postsPerHour` | `integer` | yes | How fast Flung publishes to one account, to stay inside the platform's limits. |
| `tokensExpire` | `boolean` | yes | Whether connections expire; Flung renews them before they do where the platform allows. |
| `destinations` | `boolean` | yes | Posts go to a destination chosen per post (Pinterest boards). |

## PostingTimes

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `object[]` | yes |  |
| `data[].publishAt` | `string` | yes |  |
| `data[].day` | `string` | yes |  |
| `data[].localHour` | `integer` | yes |  |
| `data[].rank` | `integer` | yes | 1 is the platform's best slot of the week. |

## Usage

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `object[]` | yes |  |
| `data[].credential` | `string` | yes | `key:<api key id>` or `app:<OAuth client id>`. |
| `data[].day` | `string` | yes |  |
| `data[].requests` | `integer` | yes |  |
| `data[].errors` | `integer` | yes | Answers of 400 or more, rate limits included. |

## Stats

| Field | Type | Required | Description |
|---|---|---|---|
| `from` | `string` | yes |  |
| `to` | `string` | yes |  |
| `posts` | `object` | yes | Posts due in the window, by status. |
| `platforms` | `object[]` | yes |  |
| `platforms[].platform` | `Platform` | yes |  |
| `platforms[].targets` | `object` | yes | Publishes to accounts on this platform, by status. |
| `accounts` | `object[]` | yes |  |
| `accounts[].platform` | `Platform` | yes |  |
| `accounts[].accounts` | `object` | yes | Accounts now, by status. |
| `media` | `object` | yes |  |
| `media.files` | `integer` | yes |  |
| `media.bytes` | `integer` | yes |  |

## InboxList

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `InboxItem[]` | yes |  |
| `data[].id` | `string` | yes |  |
| `data[].accountId` | `string` | yes |  |
| `data[].platform` | `Platform` | yes |  |
| `data[].kind` | `"comment" \| "mention"` | yes | `comment`: a reply to one of the account's posts. `mention`: the account named elsewhere. |
| `data[].status` | `"unread" \| "read" \| "replied" \| "archived"` | yes |  |
| `data[].text` | `string` | yes |  |
| `data[].author` | `object` | yes |  |
| `data[].author.id` | `string` | yes |  |
| `data[].author.name` | `string \| null` | yes |  |
| `data[].author.handle` | `string \| null` | yes |  |
| `data[].url` | `string \| null` | yes |  |
| `data[].platformItemId` | `string` | yes |  |
| `data[].platformPostId` | `string \| null` | yes | The platform post it answers. |
| `data[].targetId` | `string \| null` | yes | The Flung post target it answers, when that post went out through Flung. |
| `data[].createdAt` | `string` | yes |  |
| `data[].repliedAt` | `string \| null` | yes |  |
| `hasMore` | `boolean` | yes |  |

## InboxItem

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `accountId` | `string` | yes |  |
| `platform` | `Platform` | yes |  |
| `kind` | `"comment" \| "mention"` | yes | `comment`: a reply to one of the account's posts. `mention`: the account named elsewhere. |
| `status` | `"unread" \| "read" \| "replied" \| "archived"` | yes |  |
| `text` | `string` | yes |  |
| `author` | `object` | yes |  |
| `author.id` | `string` | yes |  |
| `author.name` | `string \| null` | yes |  |
| `author.handle` | `string \| null` | yes |  |
| `url` | `string \| null` | yes |  |
| `platformItemId` | `string` | yes |  |
| `platformPostId` | `string \| null` | yes | The platform post it answers. |
| `targetId` | `string \| null` | yes | The Flung post target it answers, when that post went out through Flung. |
| `createdAt` | `string` | yes |  |
| `repliedAt` | `string \| null` | yes |  |

## InboxSync

| Field | Type | Required | Description |
|---|---|---|---|
| `added` | `integer` | yes | New items read. |

## PostMetrics

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `object[]` | yes |  |
| `data[].targetId` | `string` | yes |  |
| `data[].accountId` | `string` | yes |  |
| `data[].platform` | `Platform` | yes |  |
| `data[].metrics` | `object \| null` | yes | null when the platform has no counts for Flung, or could not be read now (see `error`). |
| `data[].metrics.views` | `integer \| null` | yes |  |
| `data[].metrics.likes` | `integer \| null` | yes |  |
| `data[].metrics.comments` | `integer \| null` | yes |  |
| `data[].metrics.reposts` | `integer \| null` | yes |  |
| `data[].metrics.quotes` | `integer \| null` | yes |  |
| `data[].error` | `string \| null` | yes |  |
| `totals` | `object` | yes |  |
| `totals.views` | `integer \| null` | yes |  |
| `totals.likes` | `integer \| null` | yes |  |
| `totals.comments` | `integer \| null` | yes |  |
| `totals.reposts` | `integer \| null` | yes |  |
| `totals.quotes` | `integer \| null` | yes |  |

## Experience

| Field | Type | Required | Description |
|---|---|---|---|
| `xp` | `integer` | yes |  |
| `level` | `integer` | yes |  |
| `xpIntoLevel` | `integer` | yes |  |
| `xpForNextLevel` | `integer` | yes |  |
| `streakDays` | `integer` | yes | Days in a row with a published post, up to today or yesterday. |
| `leaderboardName` | `string \| null` | yes | The name on the public leaderboard; null when the workspace is not on it. |
| `achievements` | `Achievement[]` | yes |  |
| `achievements[].id` | `string` | yes |  |
| `achievements[].name` | `string` | yes |  |
| `achievements[].description` | `string` | yes |  |
| `achievements[].category` | `string` | yes |  |
| `achievements[].rarity` | `"common" \| "rare" \| "epic" \| "legendary"` | yes |  |
| `achievements[].xp` | `integer` | yes |  |
| `achievements[].unlocked` | `boolean` | yes |  |
| `achievements[].unlockedAt` | `string \| null` | yes | When Flung first saw it earned. |
| `achievements[].progress` | `object` | yes |  |
| `achievements[].progress.current` | `integer` | yes |  |
| `achievements[].progress.target` | `integer` | yes |  |

## Achievement

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `name` | `string` | yes |  |
| `description` | `string` | yes |  |
| `category` | `string` | yes |  |
| `rarity` | `"common" \| "rare" \| "epic" \| "legendary"` | yes |  |
| `xp` | `integer` | yes |  |
| `unlocked` | `boolean` | yes |  |
| `unlockedAt` | `string \| null` | yes | When Flung first saw it earned. |
| `progress` | `object` | yes |  |
| `progress.current` | `integer` | yes |  |
| `progress.target` | `integer` | yes |  |

## Leaderboard

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `object[]` | yes |  |
| `data[].rank` | `integer` | yes |  |
| `data[].name` | `string` | yes |  |
| `data[].level` | `integer` | yes |  |
| `data[].xp` | `integer` | yes |  |
| `data[].you` | `boolean` | yes |  |
