# Chat33 API

> Schedule and publish content to Instagram, Facebook, TikTok, YouTube, and X (Twitter). All endpoints support API key (x-api-key header) or session auth. Generate API keys in Settings → API Keys.

**Version:** 1.0.0
**Base URL:** `https://chat33.io/api/v1`

## Authentication

All endpoints accept an API key via the `x-api-key` header:

```
x-api-key: sk_live_your_api_key_here
```

Generate API keys at `/settings` → API Keys. Session auth (browser cookie) is also supported.

### Scopes

Every API key has these default scopes:
- `accounts:read` — list connected social channels
- `posts:read` — read post status and history
- `posts:write` — create, schedule, edit, and cancel posts
- `media:write` — upload media files

## Error Format

All errors use this shape:

```json
{
  "error": "VALIDATION_ERROR",
  "message": "Human-readable message",
  "details": [
    {
      "path": "field.name",
      "message": "what's wrong"
    }
  ]
}
```

Common error codes: `VALIDATION_ERROR` (400), `Unauthorized` (401), `Insufficient scope` (403), `Not found` (404), `CONFLICT` (409), `RATE_LIMIT` (429).

## Rate Limits

- `POST /posts`: 30/hour per organization
- `POST /media/upload`: 50/hour
- `POST /api-keys`: 10/hour per user
- Per-channel daily publishing limits: Instagram 25, Facebook 50, TikTok 20, YouTube 6

## Webhooks (callbackUrl)

When a post is published or fails, if `callbackUrl` was set on the post, a POST is sent to that URL with:

```json
{
  "postId": "post_abc123",
  "status": "PUBLISHED",
  "externalPostId": "17889341234",
  "externalPostUrl": "https://instagram.com/p/...",
  "error": null
}
```

## Accounts

Connected social media channels

### GET /accounts

**List connected accounts**

Returns all active social media channels connected to your organization.

**Required scope(s):** `accounts:read`

**Parameters:**
- `platform` (query, "instagram" | "facebook" | "tiktok" | "youtube" | "x") — Filter by platform (instagram, facebook, tiktok, youtube)

**Response 200:** List of accounts
- `items` (Account[])

**Errors:** 401, 403

## Posts

Create, schedule, and manage social media posts

### POST /posts

**Create and queue a post**

Creates a post (or one per channel for multi-platform). If `scheduledAt` is omitted, the post publishes immediately on the next cron tick (~1 min). If set, the post waits until that time. Rate limited to 30 posts/hour.

**Required scope(s):** `posts:write`

**Request body:**
- `accountIds` (string[]) **required** — Channel IDs from /accounts. One ScheduledPost is created per channel.
- `text` (string) — Caption / post text
- `mediaUrls` (string[]) — Public URLs to media. Use /media/upload first or pass any public URL.
- `mediaType` (MediaType)
- `coverImageUrl` (string) — Video thumbnail (YouTube/TikTok)
- `platformConfig` (object) — Platform-specific options (YouTube title, privacy, per-platform caption overrides)
- `callbackUrl` (string) — Webhook URL — POSTed { postId, status, externalPostId, externalPostUrl, error } on completion.
- `scheduledAt` (string) — ISO 8601 datetime. Omit to publish immediately on next cron tick (~1 min).
- `timezone` (string) — IANA timezone for scheduling (e.g. America/New_York) (default: `"UTC"`)

Example:
```json
{
  "accountIds": [
    "chan_abc123"
  ],
  "text": "Check out our new product! #launch",
  "mediaUrls": [
    "https://example.com/image.jpg"
  ],
  "mediaType": "image"
}
```

**Response 201:** Post(s) created
- `posts` (PostSummary[])
- `groupId` (string | null) — Set for multi-channel posts to group sibling posts

**Errors:** 400, 401, 403, 429

### GET /posts

**List posts**

Paginated list of posts, filterable by status, platform, or channel.

**Required scope(s):** `posts:read`

**Parameters:**
- `status` (query, "DRAFT" | "QUEUED" | "PUBLISHING" | "PUBLISHED" | "FAILED" | "CANCELLED")
- `platform` (query, "instagram" | "facebook" | "tiktok" | "youtube" | "x")
- `channelId` (query, string)
- `page` (query, integer)
- `limit` (query, integer)

**Response 200:** Paginated post list
- `items` (Post[])
- `total` (integer)
- `page` (integer)
- `limit` (integer)
- `hasMore` (boolean)

**Errors:** 401, 403

### POST /posts/validate

**Validate a post (dry run)**

Checks the post against platform rules (media format, caption length, channel validity) without creating anything. Useful for client-side preflight.

**Required scope(s):** `posts:write`

**Request body:**
- `accountIds` (string[]) **required** — Channel IDs from /accounts. One ScheduledPost is created per channel.
- `text` (string) — Caption / post text
- `mediaUrls` (string[]) — Public URLs to media. Use /media/upload first or pass any public URL.
- `mediaType` (MediaType)
- `coverImageUrl` (string) — Video thumbnail (YouTube/TikTok)
- `platformConfig` (object) — Platform-specific options (YouTube title, privacy, per-platform caption overrides)
- `callbackUrl` (string) — Webhook URL — POSTed { postId, status, externalPostId, externalPostUrl, error } on completion.
- `scheduledAt` (string) — ISO 8601 datetime. Omit to publish immediately on next cron tick (~1 min).
- `timezone` (string) — IANA timezone for scheduling (e.g. America/New_York) (default: `"UTC"`)

**Response 200:** Validation result

### GET /posts/{id}

**Get post details**

**Required scope(s):** `posts:read`

**Response 200:** Post details
- `id` (string)
- `channelId` (string)
- `groupId` (string | null)
- `status` (PostStatus)
- `text` (string | null)
- `mediaUrls` (string[])
- `mediaType` (MediaType)
- `scheduledAt` (string | null)
- `publishedAt` (string | null)
- `externalPostId` (string | null)
- `externalPostUrl` (string | null)
- `errorMessage` (string | null)
- `retryCount` (integer)
- `createdBy` (string)
- `createdAt` (string)
- `platform` (Platform)
- `accountName` (string)

**Errors:** 404

### PATCH /posts/{id}

**Update a post**

Only DRAFT and QUEUED posts can be edited. Use to reschedule, change caption, or move to draft.

**Required scope(s):** `posts:write`

**Request body:**
- `text` (string)
- `mediaUrls` (string[])
- `mediaType` (MediaType)
- `coverImageUrl` (string)
- `platformConfig` (object)
- `callbackUrl` (string)
- `scheduledAt` (string) — Set to null to publish immediately on next cron tick
- `timezone` (string) (default: `"UTC"`)
- `status` ("DRAFT" | "QUEUED" | "CANCELLED")

**Response 200:** Updated post
- `id` (string)
- `channelId` (string)
- `groupId` (string | null)
- `status` (PostStatus)
- `text` (string | null)
- `mediaUrls` (string[])
- `mediaType` (MediaType)
- `scheduledAt` (string | null)
- `publishedAt` (string | null)
- `externalPostId` (string | null)
- `externalPostUrl` (string | null)
- `errorMessage` (string | null)
- `retryCount` (integer)
- `createdBy` (string)
- `createdAt` (string)
- `platform` (Platform)
- `accountName` (string)

**Errors:** 400, 404, 409

### DELETE /posts/{id}

**Delete or cancel a post**

Hard-deletes DRAFT posts. Cancels QUEUED/PUBLISHED/FAILED posts (sets status to CANCELLED). Cannot delete a post currently being published.

**Required scope(s):** `posts:write`

**Response 200:** Deleted or cancelled

**Errors:** 404, 409

### POST /posts/{id}/duplicate

**Duplicate a post**

Clones a post for reposting or rescheduling. Defaults to a DRAFT copy on the same channel.

**Required scope(s):** `posts:write`

**Parameters:**
- `id` (path, string) **required**

**Request body:**
- `scheduledAt` (string)
- `accountIds` (string[]) — Override target channels. Defaults to original channel.
- `status` ("DRAFT" | "QUEUED") (default: `"DRAFT"`)

**Response 201:** Duplicated post(s)
- `duplicatedFrom` (string)
- `posts` (PostSummary[])
- `groupId` (string | null)

**Errors:** 404

### GET /posts/{id}/logs

**Get publish attempt logs**

Returns analytics events for this post (publish attempts, successes, failures).

**Required scope(s):** `posts:read`

**Parameters:**
- `id` (path, string) **required**

**Response 200:** Log entries
- `logs` (object[])

**Errors:** 404

## Media

Upload media files for posts

### POST /media/upload

**Upload media**

Two modes:

**1. Multipart upload** (`multipart/form-data` with `file` field) — for files under 4.5MB.

**2. URL ingestion** (`application/json` with `{url, filename}`) — fetches from a public URL, supports any size.

You can also skip this entirely and pass any public URL directly in `mediaUrls` when creating posts.

Limits: images 10MB, videos 100MB. Rate limit 50/hour.

**Required scope(s):** `media:write`

**Request body:**
- `url` (string) **required** — Public URL to fetch
- `filename` (string) — Optional filename hint

**Response 201:** Uploaded
- `url` (string)
- `type` ("image" | "video")
- `size` (integer)
- `name` (string)
- `contentType` (string)

**Errors:** 400, 429

## API Keys

Manage API keys (session auth only)

### POST /api-keys

**Create API key**

Generates a new API key. **Session auth only** — cannot be called with another API key. The raw key is returned ONCE in the response and never stored or retrievable again.

**Request body:**
- `name` (string) (default: `"Default API Key"`)

**Response 201:** Key created. Save the `key` value — it cannot be retrieved later.
- `id` (string)
- `key` (string) — The raw API key. Shown only once.
- `keyPrefix` (string)
- `name` (string)
- `scopes` (string[])
- `createdAt` (string)

**Errors:** 401, 429

### GET /api-keys

**List API keys**

Returns key prefixes and metadata. Raw keys are never returned. Session auth only.

**Response 200:** Key list
- `items` (ApiKey[])

**Errors:** 401

### DELETE /api-keys/{id}

**Revoke an API key**

Sets the key inactive. Session auth only.

**Parameters:**
- `id` (path, string) **required**

**Response 200:** Revoked
- `revoked` (true)

**Errors:** 401, 404

## Schemas

### Platform

Enum: `instagram`, `facebook`, `tiktok`, `youtube`, `x`

### MediaType

Enum: `image`, `video`, `reel`, `carousel`

### PostStatus

Enum: `DRAFT`, `QUEUED`, `PUBLISHING`, `PUBLISHED`, `FAILED`, `CANCELLED`

### Account

- `id` (string)
- `platform` (Platform)
- `name` (string)
- `username` (string | null)
- `isActive` (boolean)
- `metadata` (object)
- `createdAt` (string)

### ApiKey

- `id` (string)
- `name` (string)
- `keyPrefix` (string) — First 12 chars of the key, e.g. sk_live_abcd
- `scopes` (string[])
- `lastUsedAt` (string | null)
- `expiresAt` (string | null)
- `isActive` (boolean)
- `createdAt` (string)

### CreatePostRequest

- `accountIds` (string[]) **required** — Channel IDs from /accounts. One ScheduledPost is created per channel.
- `text` (string) — Caption / post text
- `mediaUrls` (string[]) — Public URLs to media. Use /media/upload first or pass any public URL.
- `mediaType` (MediaType)
- `coverImageUrl` (string) — Video thumbnail (YouTube/TikTok)
- `platformConfig` (object) — Platform-specific options (YouTube title, privacy, per-platform caption overrides)
- `callbackUrl` (string) — Webhook URL — POSTed { postId, status, externalPostId, externalPostUrl, error } on completion.
- `scheduledAt` (string) — ISO 8601 datetime. Omit to publish immediately on next cron tick (~1 min).
- `timezone` (string) — IANA timezone for scheduling (e.g. America/New_York) (default: `"UTC"`)

### PostSummary

- `id` (string)
- `accountId` (string)
- `platform` (Platform)
- `accountName` (string)
- `status` (PostStatus)
- `scheduledAt` (string | null)
- `createdAt` (string)

### Post

- `id` (string)
- `channelId` (string)
- `groupId` (string | null)
- `status` (PostStatus)
- `text` (string | null)
- `mediaUrls` (string[])
- `mediaType` (MediaType)
- `scheduledAt` (string | null)
- `publishedAt` (string | null)
- `externalPostId` (string | null)
- `externalPostUrl` (string | null)
- `errorMessage` (string | null)
- `retryCount` (integer)
- `createdBy` (string)
- `createdAt` (string)
- `platform` (Platform)
- `accountName` (string)

### Error

- `error` (string) — Error code, e.g. VALIDATION_ERROR, RATE_LIMIT
- `message` (string)
- `details` (object[])
