Source: https://developers.tokportal.com/account-configuration/
Markdown: https://developers.tokportal.com/account-configuration.md

# Account Configuration

Configure the account details for a bundle. Account configuration is required before a bundle can be published.

***

## Configure Account

Set or update the account details for a bundle.

```
PUT /bundles/:id/account
```

### Request Body

| Field                        | Type      | Required | Description                                                                                                                                                                                                                                        |
| ---------------------------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `username`                   | string    | Yes      | Account username. 1-24 characters, alphanumeric plus `.` and `_`.                                                                                                                                                                                  |
| `visible_name`               | string    | Yes      | Display name. 1-30 characters.                                                                                                                                                                                                                     |
| `biography`                  | string    | Yes      | Account biography. Max 80 characters.                                                                                                                                                                                                              |
| `profile_picture_url`        | string    | Yes      | URL to the profile picture image.                                                                                                                                                                                                                  |
| `link_in_bio`                | string    | No       | Link displayed in the bio. **Instagram bundles only.** On TikTok and every other platform it is still accepted, stored as `null`, and reported back in the response's `_warnings` array — read `_warnings`, not just the `200`.                    |
| `niche_warming_instructions` | string    | No       | **⚠️ Deprecated.** Legacy — kept only for accounts being re-created after a refund; use Advanced Niche Warming (`wants_advanced_warming`).                                                                                                         |
| `advanced_warming_terms`     | string\[] | No       | Backward-compatible way to set targets while the profile is editable. New integrations should use `PUT /bundles/{id}/warming-terms`, which works at any account status. Must sanitize to **exactly** the purchased count and can only be set once. |

### Validation Rules

| Field                 | Rule                                                                                                            |
| --------------------- | --------------------------------------------------------------------------------------------------------------- |
| `username`            | 1-24 characters. Allowed: `[a-zA-Z0-9_.]`                                                                       |
| `visible_name`        | 1-30 characters.                                                                                                |
| `biography`           | Max 80 characters.                                                                                              |
| `profile_picture_url` | Must be a valid URL.                                                                                            |
| `link_in_bio`         | Must be a valid URL. Stored on Instagram bundles only; accepted but nulled elsewhere, with a `_warnings` entry. |

> **WARNING: Avoid duplicated wording across accounts**
> Do not create multiple accounts that reuse the same words in the `username` or `visible_name`. Prefer unique usernames and nicknames for every account. Reusing the same words across many accounts on the same platform increases the risk of mass-ban detection by the platform's anti-spam systems.

### Requirements

- Bundle must be in `pending_setup` or `configured` status.
- You cannot modify account configuration after the bundle has been published (unpublish first).

> **WARNING: This write is a full replacement**
> `PUT /bundles/:id/account` replaces the whole profile. Any field you leave out is written back as `null`, so re-send the complete profile even when you only mean to change one part of it.

### Unrecognised fields are refused, not dropped

Because the write is a full replace, an unrecognised field name used to be quietly stripped by validation — and the value it was meant to set was wiped to `null`. A single typo (`biograhpy` instead of `biography`) therefore erased the stored biography and still returned `200`.

That no longer happens. A body carrying a field TokPortal does not recognise is rejected with `400 UNKNOWN_FIELD`, carrying `details.unknown_fields`, a `details.did_you_mean` suggestion, and `details.accepted_fields`.

The same rule applies to the other full-replace writes: [`POST /bundles`](https://developers.tokportal.com/create-bundle), [`POST /bundles/bulk`](https://developers.tokportal.com/create-bulk), `PUT /bundles/:id/account`, `PUT /bundles/:id/videos/:position`, `PUT /bundles/:id/videos/batch` and `PUT /bundles/:id/warming-terms`. On every other endpoint nothing can be lost, so the request runs normally and the unrecognised names come back in the response's `meta.ignored_fields`.

Which callers get the `400` is controlled by the server-side `API_STRICT_UNKNOWN_FIELDS` setting:

| Value               | Behaviour                                                                                                              |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `mcp` (**default**) | MCP callers are refused on full-replace endpoints; every other client keeps its `200` and reads `meta.ignored_fields`. |
| `all`               | Every caller is refused on full-replace endpoints.                                                                     |
| `off`               | No caller is ever refused; unrecognised names are always reported.                                                     |

Under the default, REST integrations see `meta.ignored_fields` today rather than a failure — treat it as the same warning and fix the field name.

### Advanced Niche Warming targets

If the bundle was created with `wants_advanced_warming: true` and a target
**count** (`advanced_warming_terms_count`), configure the targets with
`PUT /bundles/{id}/warming-terms`. This account endpoint keeps accepting the
same field for backward compatibility only while the account profile remains
editable.

- Pass `advanced_warming_terms` with exactly the purchased number of targets,
  all distinct, each 2-50 characters. Entries are trimmed and compared
  case-insensitively; anything dropped as a duplicate or for its length shrinks
  the list, and the call then fails with `400 ADVANCED_WARMING_TERMS` and
  `details.purchased_terms_count` rather than configuring fewer targets than
  you paid for.
- Configuration is **one-shot** per warming purchase — afterwards the API
  returns `409 WARMING_TERMS_ALREADY_SET`.
- Warming starts immediately when the account/order is already active (the
  `warming.session_started` webhook fires); otherwise it starts automatically
  at account submission / order accept.
- Count-only purchases never configured within **14 days** are auto-cancelled
  and fully refunded.
- On success the response includes `"_warming": { "terms_configured": true, "started": <bool> }`,
  and the account's `advanced_warming` summary reflects the new state
  (`terms_configured: true`).

### Example

```bash
curl -X PUT https://app.tokportal.com/api/ext/bundles/bnd_abc123/account \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "streetstyle.nyc",
    "visible_name": "Street Style NYC",
    "biography": "Daily street fashion from New York City.",
    "profile_picture_url": "https://cdn.example.com/profiles/streetstyle.jpg"
  }'
```

### Response

```json
{
  "data": {
    "id": "acc_xyz789",
    "bundle_id": "bnd_abc123",
    "status": "configured",
    "platform": "tiktok",
    "username": "streetstyle.nyc",
    "visible_name": "Street Style NYC",
    "biography": "Daily street fashion from New York City.",
    "profile_picture_url": "profile-pictures/bnd_abc123/streetstyle.jpg",
    "link_in_bio": null,
    "country": "US",
    "is_niche_warmed": false,
    "is_deep_warmed": false,
    "deep_warming_verification_status": null,
    "niche_warming_instructions": null,
    "advanced_warming": null,
    "feedback": null,
    "created_at": "2026-02-10T10:00:00Z"
  }
}
```

> **NOTE: Response shape**
> The account fields are returned **flat under `data`** — there is no nested `data.account` wrapper. The `id` field is the **account ID** (not the bundle ID). Use `bundle_id` to reference the parent bundle.
>
> `advanced_warming` summarizes the latest [Advanced Niche Warming](https://developers.tokportal.com/advanced-warming) session of the account (`session_id`, `status`, `terms_total`, `terms_verified`, `started_at`, `completed_at`) or is `null` when the account has none. Fetch the full per-term reports with `GET /warming-sessions/{session_id}`. The `is_niche_warmed`, `is_deep_warmed`, `deep_warming_verification_status` and `niche_warming_instructions` fields are legacy — kept only for accounts being re-created after a refund; use Advanced Niche Warming (`wants_advanced_warming`).
>
> If an external `profile_picture_url` was provided, it is automatically downloaded and re-hosted; the response will include `_info.profile_picture_rehosted: true`.

> **WARNING: Always read `_warnings`**
> A `200` does not mean every field you sent was stored. When TokPortal accepts a request but could not apply part of it, it returns a `_warnings` array of plain-language strings alongside `data`. The two cases to expect here:
>
> - `link_in_bio` sent on a bundle that is not Instagram — accepted, stored as `null`, echoed back as `null`, and reported in `_warnings`.
> - A missing profile picture.
>
> ```json
> {
>   "data": {
>     "id": "acc_xyz789",
>     "platform": "tiktok",
>     "link_in_bio": null
>   },
>   "_warnings": [
>     "link_in_bio was not stored: this is a tiktok bundle and a link in bio is an Instagram-only profile field. The response echoes link_in_bio: null."
>   ]
> }
> ```

### Instagram Example with Link in Bio

```bash
curl -X PUT https://app.tokportal.com/api/ext/bundles/bnd_d4e5f6/account \
  -H "X-API-Key: sk_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "username": "cafe.paris_",
    "visible_name": "Caf\u00e9 Paris",
    "biography": "Les meilleurs caf\u00e9s parisiens.",
    "profile_picture_url": "https://cdn.example.com/profiles/cafeparis.jpg",
    "link_in_bio": "https://cafeparis.example.com"
  }'
```

### Response

```json
{
  "data": {
    "id": "acc_pqr456",
    "bundle_id": "bnd_d4e5f6",
    "status": "configured",
    "platform": "instagram",
    "username": "cafe.paris_",
    "visible_name": "Caf\u00e9 Paris",
    "biography": "Les meilleurs caf\u00e9s parisiens.",
    "profile_picture_url": "profile-pictures/bnd_d4e5f6/cafeparis.jpg",
    "link_in_bio": "https://cafeparis.example.com",
    "country": "FR",
    "is_niche_warmed": false,
    "is_deep_warmed": false,
    "deep_warming_verification_status": null,
    "niche_warming_instructions": null,
    "advanced_warming": {
      "session_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
      "status": "in_progress",
      "terms_total": 6,
      "terms_verified": 2,
      "started_at": "2026-07-10T12:00:00Z",
      "completed_at": null
    },
    "feedback": null,
    "created_at": "2026-02-10T10:15:00Z"
  }
}
```

***

## Get Account

Retrieve the current account configuration for a bundle.

```
GET /bundles/:id/account
```

### Example

```bash
curl -X GET https://app.tokportal.com/api/ext/bundles/bnd_abc123/account \
  -H "X-API-Key: sk_xxx"
```

### Response

```json
{
  "data": {
    "id": "acc_xyz789",
    "bundle_id": "bnd_abc123",
    "status": "configured",
    "platform": "tiktok",
    "username": "streetstyle.nyc",
    "visible_name": "Street Style NYC",
    "biography": "Daily street fashion from New York City.",
    "profile_picture_url": "profile-pictures/bnd_abc123/streetstyle.jpg",
    "link_in_bio": null,
    "country": "US",
    "is_niche_warmed": false,
    "is_deep_warmed": false,
    "deep_warming_verification_status": null,
    "niche_warming_instructions": null,
    "feedback": null,
    "created_at": "2026-02-10T10:00:00Z"
  }
}
```

## Error Responses

| Status | Code                        | Description                                                                                                                                                                                                                                                                                |
| ------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `invalid_username`          | Username does not match allowed pattern `[a-zA-Z0-9_.]` or exceeds 24 characters.                                                                                                                                                                                                          |
| `400`  | `invalid_visible_name`      | Visible name is empty or exceeds 30 characters.                                                                                                                                                                                                                                            |
| `400`  | `biography_too_long`        | Biography exceeds 80 characters.                                                                                                                                                                                                                                                           |
| `400`  | `invalid_url`               | `profile_picture_url` or `link_in_bio` is not a valid URL.                                                                                                                                                                                                                                 |
| `400`  | `UNKNOWN_FIELD`             | The body carries a field TokPortal does not recognise. This write is a full replace, so the name is refused rather than dropped — see [Unrecognised fields are refused, not dropped](https://developers.tokportal.com/account-configuration/#unrecognised-fields-are-refused-not-dropped). |
| `400`  | `ADVANCED_WARMING_TERMS`    | `advanced_warming_terms` does not sanitize to exactly the purchased target count (`details.purchased_terms_count`).                                                                                                                                                                        |
| `409`  | `invalid_status`            | Bundle is not in `pending_setup` or `configured` status.                                                                                                                                                                                                                                   |
| `409`  | `DUPLICATE_ACCOUNT_BUNDLE`  | An active bundle already exists for this username on the same platform. `details.existing_bundle_id` contains the conflicting bundle. Does not apply to `videos_only` bundles.                                                                                                             |
| `409`  | `WARMING_TERMS_ALREADY_SET` | The Advanced Niche Warming targets are already configured (or the session already started). Targets can only be set once per warming purchase.                                                                                                                                             |
| `404`  | `bundle_not_found`          | Bundle does not exist.                                                                                                                                                                                                                                                                     |
| `404`  | `WARMING_SESSION_NOT_FOUND` | `advanced_warming_terms` was passed but the bundle has no warming session to configure (e.g. created without Advanced Niche Warming).                                                                                                                                                      |
