# Accounts over WebSocket

An account is a posting handle on TikTok, Instagram or YouTube, bound to a phone slot.

**Accounts are linked, not signed in.** The handle must already be signed in on the phone.
These funs record it, move it between slots and remove it from the farm. There is no login,
no logout and no account status anywhere in this API.

All five funs need the `accounts` ability. See [Frames](/websocket/frames) for the request and
reply envelope.

| Fun | Data fields | Reply |
|---|---|---|
| `/app/accounts/list` | `page`, `platform` | `200` with `{data, meta}` |
| `/app/accounts/get` | `account` | `200` with one account |
| `/app/accounts/create` | `platform`, `handle`, `slot`, `notes`, `google_email` | `201` with the created account |
| `/app/accounts/update` | `account`, plus fields to change | `200` with the updated account |
| `/app/accounts/delete` | `account` | `204` with `data: null` |

## The account shape

```json
{
  "id": "6f2c8a41-95c1-4a2e-8f64-0b8d5e1c7a93",
  "platform": "tiktok",
  "handle": "@studio.nine",
  "slot": "b3f1c7e2-4a90-4d6f-9d1b-2c8e5f7a0d31",
  "notes": "morning shift",
  "created_at": "2026-01-20T11:02:41+00:00",
  "updated_at": "2026-02-11T08:55:10+00:00"
}
```

`google_email` appears on YouTube accounts only. An account id is a UUID that identifies its
own platform, so `/app/accounts/get`, `/app/accounts/update` and `/app/accounts/delete` never
ask which platform it is on.

## What differs per platform

| | TikTok | Instagram | YouTube |
|---|---|---|---|
| `handle` | Required | Required | Required |
| `slot` | Required, at most 4 accounts a phone | Optional | Optional |
| `google_email` | Rejected | Rejected | Required |

`platform` defaults to `tiktok` when you leave it out. The cap of 4 accounts a phone counts
TikTok accounts only, and is checked on TikTok creates and moves.

## List accounts

| Field | Type | | Description |
|---|---|---|---|
| `page` | integer | optional | Page number, 1 or more. Defaults to 1. |
| `platform` | string | optional | Narrow to `tiktok`, `instagram` or `youtube`. Omit for every platform. |

The reply is `{"data": [...], "meta": {"current_page", "last_page", "per_page", "total"}}`,
50 accounts a page, newest first. Every account carries its own `platform`.

## Get an account

| Field | Type | | Description |
|---|---|---|---|
| `account` | string | **required** | Account UUID, on any platform. |

The field is `account`, not `account_id`. An id you cannot see answers `404`.

## Create an account

| Field | Type | | Description |
|---|---|---|---|
| `platform` | string | optional | `tiktok`, `instagram` or `youtube`. Defaults to `tiktok`. |
| `handle` | string | **required** | Public @handle, up to 255 characters. |
| `slot` | string | *see above* | Phone slot UUID the account is bound to. Required on TikTok. |
| `notes` | string | optional | Free-form notes, up to 2000 characters. |
| `google_email` | string | *see above* | The Google account address of the channel. Required on YouTube, rejected elsewhere. |

The reply is `201` with the created account.

Naming a slot means you must be able to control that phone, or the call is refused with `403`
and `You do not have permission to control the phone for this slot.` That check runs before
anything else, so a slot you cannot control never reports how full it is.

A fifth TikTok account on one phone is refused with `422` and
`This phone already holds the maximum of 4 accounts.`

Omitting `google_email` on YouTube is refused with `422`. Sending it on any other platform is
refused too, with `The google email field is prohibited.`

## Update an account

| Field | Type | | Description |
|---|---|---|---|
| `account` | string | **required** | Account UUID. |
| `handle` | string | optional | New @handle. Cannot be blanked. |
| `slot` | string | optional | Phone slot to move the account to. |
| `notes` | string | optional | New notes, or `null` to clear them. |
| `google_email` | string | optional | Editable on YouTube only. |

Only the fields you send change. The platform cannot be changed here: the account lives on
one platform for its whole life.

Moving an account onto a phone needs a control grant on the destination slot, the same as
creating one. Owning the account is not enough.

## Delete an account

| Field | Type | | Description |
|---|---|---|---|
| `account` | string | **required** | Account UUID. |

The reply is `204` with `data: null`. This removes the account from the farm. It does not
delete anything on TikTok, Instagram or YouTube.

## Worked example: link, list, edit, remove

<CodeTabs syncKey="lang">

```python title="Python"
from zerobull import ZeroBull

with ZeroBull() as client, client.socket() as socket:
    slot = socket.phones.list()[0].slot

    account = socket.accounts.create(handle="@studio.nine", slot=slot, notes="morning shift")
    print(account.id, account.platform, account.slot)

    channel = socket.accounts.create(
        handle="@studio.nine",
        platform="youtube",
        google_email="studio.nine@example.com",
    )
    print(channel.google_email)

    for linked in socket.accounts.list(platform="tiktok").iter_all():
        print(linked.handle, linked.slot)

    account = socket.accounts.update(account.id, notes="evening shift")
    socket.accounts.delete(account.id)
```

```typescript title="TypeScript"
import { ZeroBull } from "@0bull/sdk";

const client = new ZeroBull();
await using socket = client.socket();
await socket.connect();

const phones = await socket.phones.list();
const slot = phones[0]!.slot;

const account = await socket.accounts.create({
  handle: "@studio.nine",
  slot,
  notes: "morning shift",
});
console.log(account.id, account.platform, account.slot);

const channel = await socket.accounts.create({
  handle: "@studio.nine",
  platform: "youtube",
  google_email: "studio.nine@example.com",
});
console.log(channel.google_email);

for await (const linked of await socket.accounts.list({ platform: "tiktok" })) {
  console.log(linked.handle, linked.slot);
}

await socket.accounts.update(account.id, { notes: "evening shift" });
await socket.accounts.delete(account.id);
```

```json title="Raw frames"
→ { "fun": "/app/accounts/create", "msgid": "1",
    "data": { "handle": "@studio.nine", "slot": "b3f1c7e2-...", "notes": "morning shift" } }
← { "fun": "/app/accounts/create", "msgid": "1", "status": 201,
    "data": { "id": "6f2c8a41-...", "platform": "tiktok", "handle": "@studio.nine",
              "slot": "b3f1c7e2-...", "notes": "morning shift",
              "created_at": "2026-02-11T09:02:00+00:00",
              "updated_at": "2026-02-11T09:02:00+00:00" } }

→ { "fun": "/app/accounts/create", "msgid": "2",
    "data": { "platform": "youtube", "handle": "@studio.nine",
              "google_email": "studio.nine@example.com" } }
← { "fun": "/app/accounts/create", "msgid": "2", "status": 201,
    "data": { "id": "c1a7e930-...", "platform": "youtube", "handle": "@studio.nine",
              "slot": null, "notes": null,
              "google_email": "studio.nine@example.com",
              "created_at": "2026-02-11T09:02:01+00:00",
              "updated_at": "2026-02-11T09:02:01+00:00" } }

→ { "fun": "/app/accounts/list", "msgid": "3", "data": { "platform": "tiktok" } }
← { "fun": "/app/accounts/list", "msgid": "3", "status": 200,
    "data": { "data": [ { "id": "6f2c8a41-...", "platform": "tiktok", "handle": "@studio.nine",
                          "slot": "b3f1c7e2-...", "notes": "morning shift",
                          "created_at": "2026-02-11T09:02:00+00:00",
                          "updated_at": "2026-02-11T09:02:00+00:00" } ],
              "meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 1 } } }

→ { "fun": "/app/accounts/update", "msgid": "4",
    "data": { "account": "6f2c8a41-...", "notes": "evening shift" } }
← { "fun": "/app/accounts/update", "msgid": "4", "status": 200,
    "data": { "id": "6f2c8a41-...", "platform": "tiktok", "handle": "@studio.nine",
              "slot": "b3f1c7e2-...", "notes": "evening shift",
              "created_at": "2026-02-11T09:02:00+00:00",
              "updated_at": "2026-02-11T09:04:12+00:00" } }

→ { "fun": "/app/accounts/delete", "msgid": "5", "data": { "account": "6f2c8a41-..." } }
← { "fun": "/app/accounts/delete", "msgid": "5", "status": 204, "data": null }
```

</CodeTabs>
