X-Trader labels API
X-Trader labels are a per-account set of up to 20 named, coloured tags shared by sniper, copy-trade and transfer-snipe tasks. They are managed at /labels (list, create, get, update, delete) and bound to tasks through the risk_label_id field. Label names are unique per account, labels do not use ETag/If-Match, and templates never take a label.
Primary source: X-Trader API reference. This page is an independent explanation of X-Trader Public API v1.0.0; it is not affiliated with X-Trader.
Endpoints
Section titled “Endpoints”| Method and path | Required scope | Headers | Success |
|---|---|---|---|
GET /labels |
sniper:read or copytrade:read |
Authorization |
200, all labels |
POST /labels |
sniper:write or copytrade:write |
Authorization, Idempotency-Key |
201, created label |
GET /labels/{id} |
sniper:read or copytrade:read |
Authorization |
200, one label |
PATCH /labels/{id} |
sniper:write or copytrade:write |
Authorization |
200, updated label |
DELETE /labels/{id} |
sniper:write or copytrade:write |
Authorization |
204, empty |
Either product scope is sufficient. The spec lists no transfersnipe:* scope for label endpoints, even though transfer-snipe tasks can be labelled, so a transfer-snipe-only token cannot manage labels. Deletion uses a :write scope; there is no label :delete scope. Responses carry X-Request-Id but no ETag.
Label fields
Section titled “Label fields”LabelResource (returned)
Section titled “LabelResource (returned)”| Field | Type | Meaning |
|---|---|---|
id |
UUID | Server-generated label id |
name |
string, 1-32 characters | Display name, trimmed |
color |
string, ^#[0-9A-Fa-f]{6}$ |
Colour as #RRGGBB |
sort_order |
integer | Display order (0-32767 on input) |
sniper_count |
integer, 0 or more | Sniper configs using this label |
copy_trade_count |
integer, 0 or more | Copy-trade configs using this label |
transfer_snipe_count |
integer, 0 or more | Transfer-snipe tasks using this label |
created_at |
ISO 8601 date-time with UTC offset | Creation time |
updated_at |
ISO 8601 date-time with UTC offset | Last change |
LabelCreate (POST body)
Section titled “LabelCreate (POST body)”| Field | Type | Default | Constraints |
|---|---|---|---|
name |
string | none | 1-32 characters after trimming leading and trailing spaces; unique per account |
color |
string | #6366f1 |
#RRGGBB |
sort_order |
integer | 0 |
0-32767 |
The schema lists all three as required while also giving defaults for color and sort_order; the spec’s own curl example sends only name and color. Sending all three avoids any ambiguity.
LabelPatch (PATCH body)
Section titled “LabelPatch (PATCH body)”Any of name, color, sort_order with the same constraints; at least one field is required. Unknown fields and user_id are rejected with validation_error.
Rules and limits
Section titled “Rules and limits”| Rule | Behaviour |
|---|---|
| Maximum labels per account | 20; the 21st create returns quota_exceeded |
| Unique names | Duplicate name on create or PATCH returns label_name_exists |
| Shared set | One set for sniper, copy-trade and transfer-snipe tasks |
| List order | sort_order, then created_at |
| Pagination | None; GET /labels returns all labels in data.items |
| Concurrency | No ETag / If-Match on labels; last write applies |
| Idempotency | Idempotency-Key required on POST /labels (8-128 chars, A-Za-z0-9._:-, kept 24 h) |
| Renaming | Bound tasks keep the same label id |
| Delete | Tasks are unassigned (ON DELETE SET NULL), not deleted |
| Foreign id | Label owned by another account returns 404 not_found |
Binding labels to tasks with risk_label_id
Section titled “Binding labels to tasks with risk_label_id”Sniper, copy-trade and transfer-snipe configs have a nullable risk_label_id field.
| Action | How |
|---|---|
| Bind on create | Include "risk_label_id": "<label uuid>" in the create body |
| Change binding | PATCH /{product}/configs/{id} with risk_label_id and the current If-Match |
| Remove binding | PATCH with "risk_label_id": null and the current If-Match |
| Filter lists | GET /{product}/configs?label_id=<uuid> or label_id=unlabeled |
Binding or clearing a label is allowed while the task is active; only config and linked_wallets changes require pausing. The request still needs the latest If-Match. A risk_label_id that does not exist or belongs to another account returns 404 not_found.
Templates never accept or return risk_label_id; sending it in a template body is rejected. Labels are applied per task, not per template.
Examples
Section titled “Examples”Create a label:
curl --request POST 'https://x-trader.ai/api/v1/labels' \ --header "Authorization: Bearer $X_TRADER_API_TOKEN" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: label-create-001' \ --data '{"name":"Watchlist","color":"#6366f1","sort_order":0}'Rename and recolour (no If-Match):
curl --request PATCH "https://x-trader.ai/api/v1/labels/$LABEL_ID" \ --header "Authorization: Bearer $X_TRADER_API_TOKEN" \ --header 'Content-Type: application/json' \ --data '{"name":"High priority","color":"#ef4444"}'Bind the label to a sniper config:
curl --request PATCH "https://x-trader.ai/api/v1/sniper/configs/$CONFIG_ID" \ --header "Authorization: Bearer $X_TRADER_API_TOKEN" \ --header 'Content-Type: application/json' \ --header 'If-Match: "v3"' \ --data "{\"risk_label_id\":\"$LABEL_ID\"}"Delete a label:
curl --request DELETE "https://x-trader.ai/api/v1/labels/$LABEL_ID" \ --header "Authorization: Bearer $X_TRADER_API_TOKEN"Python: get or create a label by name, then bind it:
import osimport uuidimport requests
BASE = "https://x-trader.ai/api/v1"H = {"Authorization": f"Bearer {os.environ['X_TRADER_API_TOKEN']}"}
def get_or_create_label(name, color="#6366f1"): r = requests.get(f"{BASE}/labels", headers=H, timeout=10) r.raise_for_status() for label in r.json()["data"]["items"]: if label["name"] == name: return label r = requests.post( f"{BASE}/labels", headers={**H, "Idempotency-Key": f"label-{uuid.uuid4()}"}, json={"name": name, "color": color, "sort_order": 0}, timeout=10, ) r.raise_for_status() # quota_exceeded once 20 labels exist return r.json()["data"]
def bind_label(product, config_id, label_id): r = requests.get(f"{BASE}/{product}/configs/{config_id}", headers=H, timeout=10) r.raise_for_status() r = requests.patch( f"{BASE}/{product}/configs/{config_id}", headers={**H, "If-Match": r.headers["ETag"]}, json={"risk_label_id": label_id}, # None to unassign timeout=10, ) r.raise_for_status() return r.json()["data"]
label = get_or_create_label("Watchlist")bind_label("copy-trade", "00000000-0000-4000-8000-000000000000", label["id"])How many labels can an X-Trader account have?
Section titled “How many labels can an X-Trader account have?”- Creating the 21st label returns
quota_exceeded. Sniper, copy-trade and transfer-snipe tasks share this one set.
Can two labels have the same name?
Section titled “Can two labels have the same name?”No. Names are unique per account; creating or renaming to an existing name returns label_name_exists.
Do label updates need If-Match?
Section titled “Do label updates need If-Match?”No. Labels do not use ETag or If-Match. Creating a label still requires an Idempotency-Key header.
How do I attach a label to a task?
Section titled “How do I attach a label to a task?”Send risk_label_id with the label UUID when creating a config, or in a PATCH with the config’s current If-Match. Send risk_label_id=null to remove it. The task does not need to be paused.
What happens to tasks when I delete a label?
Section titled “What happens to tasks when I delete a label?”They are not deleted. Their risk_label_id is set to null (ON DELETE SET NULL).