# X-Trader copy-trade configs API

URL: https://docs.blizz.id/xtrader/copy-trade/
Updated: 2026-10-16

The copy-trade configs API is the set of seven operations under `/copy-trade/configs` that manage the caller's own copy-trade tasks: list, create, get, patch, delete, pause and resume. Each configuration follows one `target_wallet`, can carry up to 20 `linked_wallets`, and is always created paused (`is_active=false`) until `POST /copy-trade/configs/{id}/resume`. Reads need `copytrade:read`, writes `copytrade:write`, delete `copytrade:delete`.

Primary source: [X-Trader API reference](https://x-trader.ai/api/docs). This page is an independent explanation of X-Trader Public API v1.0.0; it is not affiliated with X-Trader.

## Operations

| Method and path | Purpose | Scope | Required headers | Success |
|---|---|---|---|---|
| `GET /copy-trade/configs` | List owned configurations | `copytrade:read` | `Authorization` | `200` list envelope |
| `POST /copy-trade/configs` | Create a paused configuration | `copytrade:write` | `Authorization`, `Idempotency-Key` | `201` resource envelope |
| `GET /copy-trade/configs/{id}` | Read one configuration | `copytrade:read` | `Authorization` | `200` resource envelope |
| `PATCH /copy-trade/configs/{id}` | Update mutable fields | `copytrade:write` | `Authorization`, `If-Match` | `200` resource envelope |
| `DELETE /copy-trade/configs/{id}` | Delete a paused configuration | `copytrade:delete` | `Authorization`, `If-Match` | `204` no body |
| `POST /copy-trade/configs/{id}/pause` | Stop execution | `copytrade:write` | `Authorization`, `If-Match` | `200` resource envelope |
| `POST /copy-trade/configs/{id}/resume` | Revalidate and start execution | `copytrade:write` | `Authorization`, `If-Match` | `200` resource envelope |

Base URL: `https://x-trader.ai/api/v1`. Every operation also lists `400`, `401`, `403`, `404`, `409`, `429`, `500` and `503`. Bulk delete is not available. Note the scope spelling: `copytrade:*` without a hyphen, while the path uses `copy-trade`.

## Headers

| Header | Where | Rule |
|---|---|---|
| `Authorization` | All requests | `Bearer xt_live_<id>.<secret>` |
| `Idempotency-Key` | `POST /copy-trade/configs` | Required. 8-128 characters from `A-Z a-z 0-9 . _ : -`, kept 24 hours. Same key with a different body: `idempotency_conflict`. |
| `If-Match` | PATCH, DELETE, pause, resume | Required. Latest `ETag`, format `"v<number>"`. Stale value: `version_conflict`. |
| `ETag` (response) | 201 and 200 single-resource responses | Version to send on the next write |
| `X-Request-Id` (response) | Single-resource and delete responses | Support correlation ID |

See [/xtrader/write-rules/](/xtrader/write-rules/) for body size, decimal-string and strict-object rules.

## List: GET /copy-trade/configs

Only the token owner's configurations, ordered by `updated_at` and `id`.

| Query parameter | Type | Rule |
|---|---|---|
| `limit` | integer | 1-100, default 20 |
| `cursor` | string | Max 2048 characters; pass `next_cursor` unchanged. Bound to token and filters. |
| `status` | string | `active` or `paused` |
| `q` | string | Max 64 characters; searches `remark` and `target_wallet` |
| `label_id` | string | Owned label UUID or `unlabeled`; foreign or missing UUID returns `404` |

Response: `data.items` (array of `CopyTradeResource`), `data.next_cursor` (string or null), `meta.request_id`. See [/xtrader/pagination/](/xtrader/pagination/).

## Create: POST /copy-trade/configs

Body (`CopyTradeCreate`). Strict object; `user_id`, `version`, `template` and any unknown field are rejected.

| Field | Type | Required | Rule |
|---|---|---|---|
| `remark` | string | yes (default `""`) | Max 64 characters, trimmed |
| `target_wallet` | string | yes | Solana address, 32-44 characters. The wallet being copied. Immutable after creation. |
| `linked_wallets` | array of string | yes (default `[]`) | Max 20 items, each a unique Solana address of 32-44 characters |
| `risk_label_id` | string (UUID) or null | no | Label owned by the account; see [/xtrader/labels/](/xtrader/labels/) |
| `config` | object | yes | Complete copy-trade strategy; field reference at [/xtrader/config-copy-trade/](/xtrader/config-copy-trade/) |

Required keys in `config`: `buy_config_mode`, `buy_config_universal`, `buy_config_tiers`, `selected_dexes`, `dex_source`, `buy_token_once`, `first_buy_only`, `reentry_after_flat`, `buy_once`, `skip_dev_launch`, `dev_sell_only`, `dev_sell_all_only`, `address_blacklist`, `min_trade_amount`, `max_trade_amount`, `auto_sell`, `auto_strategy`, `timed_strategy`. Sell-side objects: [/xtrader/auto-sell/](/xtrader/auto-sell/), [/xtrader/auto-strategy/](/xtrader/auto-strategy/), [/xtrader/timed-strategy/](/xtrader/timed-strategy/).

### linked_wallets

The spec defines `linked_wallets` as unique Solana addresses linked to the copy-trade target, with a maximum of 20. It does not further describe how execution uses them. Rules that follow from the schema and operation text:

- Duplicates inside the array are not allowed (the items must be unique).
- On PATCH the array replaces the stored array completely; it is not appended to.
- Changing it requires the configuration to be paused.
- Do not confuse it with `config.address_blacklist.wallets` (max 50), which is a separate copy-trade filter inside `config`.

### Target wallet uniqueness

The error enum contains `target_already_exists`. The spec does not spell out the uniqueness scope; the code name indicates that the `target_wallet` already has a configuration. Practical handling: search with `GET /copy-trade/configs?q=<target_wallet>`, then pause and PATCH the existing configuration instead of creating another. The created configuration also counts toward the shared configuration quota shown by `GET /me` (`quota_exceeded` when it is full).

### Example: create

```bash
curl --request POST 'https://x-trader.ai/api/v1/copy-trade/configs' \
  --header "Authorization: Bearer $X_TRADER_API_TOKEN" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: create-copy-20261016-001' \
  --data '{
    "remark": "Primary copy-trade target",
    "target_wallet": "11111111111111111111111111111111",
    "linked_wallets": [],
    "config": {
      "buy_config_mode": "universal",
      "buy_config_universal": {
        "market_cap_min": "", "market_cap_max": "", "amount_sol": "0.1",
        "jito_tip": "0.01", "jito_priority_fee": "0.0005", "jito_slippage": "15", "jito_bundle_amount": "",
        "priority_fee": "0.005", "priority_tip": "0.001", "priority_slippage": "15", "priority_bundle_amount": "",
        "sol_slippage_mode": false
      },
      "buy_config_tiers": [],
      "selected_dexes": ["pump-fun"],
      "dex_source": "global",
      "buy_token_once": false,
      "first_buy_only": false,
      "reentry_after_flat": false,
      "buy_once": false,
      "skip_dev_launch": false,
      "dev_sell_only": false,
      "dev_sell_all_only": false,
      "address_blacklist": {"enabled": false, "wallets": []},
      "min_trade_amount": "",
      "max_trade_amount": "",
      "auto_sell": {
        "enabled": false, "tab": "tpsl", "expiry_time": "10",
        "tp_orders": [{"trigger": "90", "sell": "100"}], "sl_orders": [],
        "follow_sell": false, "proportional_sell": false,
        "trailing_take_profit": {"enabled": false, "tiers": []}
      },
      "auto_strategy": {"migration_clear": false, "timed_sell": false, "sell_delay": "30"},
      "timed_strategy": {"enabled": false, "orders": []}
    }
  }'
```

Response `201 Created` (headers `ETag: "v1"`, `X-Request-Id`; IDs and timestamps illustrative):

```json
{
  "data": {
    "id": "7a1c2e9d-0b3f-4d6a-8e5c-2f9b1d7a4c60",
    "remark": "Primary copy-trade target",
    "target_wallet": "11111111111111111111111111111111",
    "is_active": false,
    "risk_label_id": null,
    "version": 1,
    "created_at": "2026-10-16T09:00:00Z",
    "updated_at": "2026-10-16T09:00:00Z",
    "linked_wallets": [],
    "config": { "buy_config_mode": "universal", "...": "stored config as sent" }
  },
  "meta": {
    "request_id": "req_0123456789abcdef",
    "persistence": "committed",
    "propagation": "eventual"
  }
}
```

## Get: GET /copy-trade/configs/{id}

`id` is a UUID. Missing IDs and IDs owned by other users both return `404 not_found`. The stored snapshot is returned as is and may include legacy web-UI fields; create, update and resume still enforce the current strict schema.

## Patch: PATCH /copy-trade/configs/{id}

Body (`CopyTradePatch`): at least one of `remark`, `linked_wallets`, `risk_label_id`, `config`.

| Field | Allowed while active | Behaviour |
|---|---|---|
| `remark` | yes | Replaces the note |
| `risk_label_id` | yes | Binds a label; `null` unassigns |
| `linked_wallets` | no, pause first | Replaces the whole array (max 20) |
| `config` | no, pause first | Replaces the whole config; must pass the full schema |
| `target_wallet` | never | Immutable, not part of the patch schema |

The error enum provides `config_must_be_paused` for writes that require a paused configuration.

### Python: replace linked_wallets

```python
import os
import requests

BASE = "https://x-trader.ai/api/v1"
H = {"Authorization": f"Bearer {os.environ['X_TRADER_API_TOKEN']}"}
cid = "7a1c2e9d-0b3f-4d6a-8e5c-2f9b1d7a4c60"

r = requests.get(f"{BASE}/copy-trade/configs/{cid}", headers=H)
r.raise_for_status()
etag, was_active = r.headers["ETag"], r.json()["data"]["is_active"]

if was_active:
    r = requests.post(f"{BASE}/copy-trade/configs/{cid}/pause", headers={**H, "If-Match": etag})
    r.raise_for_status()
    etag = r.headers["ETag"]

r = requests.patch(
    f"{BASE}/copy-trade/configs/{cid}",
    json={"linked_wallets": ["So11111111111111111111111111111111111111112"]},
    headers={**H, "If-Match": etag},
)
r.raise_for_status()
etag = r.headers["ETag"]

if was_active:
    r = requests.post(f"{BASE}/copy-trade/configs/{cid}/resume", headers={**H, "If-Match": etag})
    r.raise_for_status()
```

## Pause and resume

Pause changes the stored state immediately; execution nodes observe it eventually. Resume revalidates the full stored config before activation and refuses invalid historical data. Both take no body and require `If-Match`.

### Example: resume

```bash
curl --request POST 'https://x-trader.ai/api/v1/copy-trade/configs/7a1c2e9d-0b3f-4d6a-8e5c-2f9b1d7a4c60/resume' \
  --header "Authorization: Bearer $X_TRADER_API_TOKEN" \
  --header 'If-Match: "v1"'
```

Response `200 OK` (new `ETag` header; values illustrative):

```json
{
  "data": {
    "id": "7a1c2e9d-0b3f-4d6a-8e5c-2f9b1d7a4c60",
    "remark": "Primary copy-trade target",
    "target_wallet": "11111111111111111111111111111111",
    "is_active": true,
    "risk_label_id": null,
    "version": 2,
    "created_at": "2026-10-16T09:00:00Z",
    "updated_at": "2026-10-16T09:01:10Z",
    "linked_wallets": [],
    "config": { "buy_config_mode": "universal", "...": "unchanged stored config" }
  },
  "meta": {
    "request_id": "req_0123456789abcdef",
    "persistence": "committed",
    "propagation": "eventual"
  }
}
```

Use the returned `ETag` header for the next write instead of deriving it from `version`.

## Delete: DELETE /copy-trade/configs/{id}

Needs `copytrade:delete` and `If-Match`. Active configurations cannot be deleted; pause first. Success is `204` with no body.

## Lifecycle

1. Create (paused) with `Idempotency-Key`; keep the `ETag`.
2. Resume with that `ETag`.
3. For `config` or `linked_wallets` changes: pause, PATCH with full values, resume.
4. `remark` and `risk_label_id` can change at any time with the latest `If-Match`.
5. To remove: pause, then delete.

## Response envelope and errors

`CopyTradeResource` fields: `id`, `remark`, `target_wallet`, `is_active`, `risk_label_id`, `version`, `created_at`, `updated_at`, `linked_wallets`, `config`. Errors: `{ "error": { "code", "message", "fields"? }, "request_id" }`.

| Code | Typical cause on /copy-trade/configs |
|---|---|
| `validation_error` | Unknown field, more than 20 `linked_wallets`, duplicate or malformed address, JSON number instead of decimal string, failed resume revalidation |
| `target_already_exists` | Target wallet already used by an existing configuration |
| `insufficient_scope` | Missing `copytrade:read`, `copytrade:write` or `copytrade:delete` |
| `not_found` | Unknown or foreign ID, foreign label |
| `config_must_be_paused` | `config` or `linked_wallets` change, or delete, on an active configuration |
| `version_conflict` | Stale `If-Match` |
| `idempotency_required` / `idempotency_conflict` | Missing key, or key reused with another body |
| `quota_exceeded` | Shared configuration quota full |
| `rate_limit_exceeded`, `service_unavailable` | `429` / `503`, honour `Retry-After` |

The spec does not publish a mapping from each code to an HTTP status. See [/xtrader/errors/](/xtrader/errors/).

## FAQ

### How many linked_wallets can a copy-trade config have?

Up to 20. Each entry is a unique Solana address of 32-44 characters, and the field defaults to an empty array.

### What does target_already_exists mean on copy-trade create?

It is the error code the API uses when the target wallet is already in use by an existing configuration. Find that configuration with `GET /copy-trade/configs?q=<address>` and update it instead of creating a second one.

### Can I edit linked_wallets while the copy-trade is active?

No. Pause the configuration before changing `config` or `linked_wallets`. Only `remark` and `risk_label_id` can change while it is active.

### Does PATCH linked_wallets add to the existing list?

No. `linked_wallets` in a PATCH replaces the whole stored array, so send the complete list you want to keep.

### Which scopes does the copy-trade API need?

`copytrade:read` for list and get, `copytrade:write` for create, patch, pause and resume, and `copytrade:delete` for delete.
