X-Trader sniper configs API
The sniper configs API is the set of seven operations under /sniper/configs that manage the caller’s own sniper tasks: list, create, get, patch, delete, pause and resume. A configuration is always created paused (is_active=false) and starts executing only after POST /sniper/configs/{id}/resume. Reads need sniper:read, writes need sniper:write, and delete needs sniper:delete.
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.
Operations
Section titled “Operations”| Method and path | Purpose | Scope | Required headers | Success |
|---|---|---|---|---|
GET /sniper/configs |
List owned configurations | sniper:read |
Authorization |
200 list envelope |
POST /sniper/configs |
Create a paused configuration | sniper:write |
Authorization, Idempotency-Key |
201 resource envelope |
GET /sniper/configs/{id} |
Read one configuration | sniper:read |
Authorization |
200 resource envelope |
PATCH /sniper/configs/{id} |
Update mutable fields | sniper:write |
Authorization, If-Match |
200 resource envelope |
DELETE /sniper/configs/{id} |
Delete a paused configuration | sniper:delete |
Authorization, If-Match |
204 no body |
POST /sniper/configs/{id}/pause |
Stop execution | sniper:write |
Authorization, If-Match |
200 resource envelope |
POST /sniper/configs/{id}/resume |
Revalidate and start execution | sniper: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 as possible error statuses. Bulk delete does not exist.
Headers
Section titled “Headers”| Header | Where | Rule |
|---|---|---|
Authorization |
All requests | Bearer xt_live_<id>.<secret>. The user is derived from the token only. |
Idempotency-Key |
POST /sniper/configs |
Required. 8-128 characters from A-Z a-z 0-9 . _ : -. Kept for 24 hours; the same key with a different body is rejected (idempotency_conflict). |
If-Match |
PATCH, DELETE, pause, resume | Required. The latest ETag of the resource, matching "v<number>", for example "v3". A stale value returns version_conflict. |
ETag (response) |
201 and 200 single-resource responses | Current version. Send it as If-Match on the next write. |
X-Request-Id (response) |
Single-resource and delete responses | Request identifier for support. |
General write rules (64 KiB body limit, decimal strings, strict objects) are covered on /xtrader/write-rules/.
List: GET /sniper/configs
Section titled “List: GET /sniper/configs”Returns only configurations owned by the token’s user, ordered by updated_at and id so that cursor pagination is stable.
| Query parameter | Type | Rule |
|---|---|---|
limit |
integer | 1-100, default 20 |
cursor |
string | Max 2048 characters. Opaque next_cursor from the previous page, bound to the token and filters. |
status |
string | active or paused |
q |
string | Max 64 characters. Searches remark and target_wallet within the caller’s data. |
label_id |
string | A label UUID owned by the account, or the literal unlabeled. A foreign or missing UUID returns 404. |
The response is { "data": { "items": [SniperResource...], "next_cursor": string or null }, "meta": { "request_id": ... } }. Restart from the first page after changing a filter. Details: /xtrader/pagination/.
Create: POST /sniper/configs
Section titled “Create: POST /sniper/configs”Request body (SniperCreate, strict, unknown fields rejected; user_id, version and template are explicitly rejected):
| Field | Type | Required | Rule |
|---|---|---|---|
remark |
string | yes (default "") |
Max 64 characters, trimmed. |
target_wallet |
string | yes | Solana address, 32-44 characters. Immutable after creation. |
risk_label_id |
string (UUID) or null | no | Label owned by the same account. See /xtrader/labels/. |
config |
object | yes | Complete sniper strategy. Field reference: /xtrader/config-sniper/. |
Required keys inside config: buy_amount, jito_mode, priority_mode, selected_dexes, dex_source, buy_once, sol_slippage_mode, snipe_external, auto_sell, auto_strategy, timed_strategy. The sell-side objects are documented on /xtrader/auto-sell/, /xtrader/auto-strategy/ and /xtrader/timed-strategy/.
The spec describes target_wallet only as a valid Solana address; it does not define its role in sniper execution beyond that. A successful create returns 201 with is_active: false, an ETag (initially "v1" in the spec’s header example) and the stored resource. The new configuration counts toward the shared configuration quota reported by GET /me (quota.max, quota.used, quota.remaining), which sniper, copy-trade and transfer-snipe tasks share.
Example: create
Section titled “Example: create”curl --request POST 'https://x-trader.ai/api/v1/sniper/configs' \ --header "Authorization: Bearer $X_TRADER_API_TOKEN" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: create-sniper-20261016-001' \ --data '{ "remark": "Primary Pump sniper", "target_wallet": "11111111111111111111111111111111", "config": { "buy_amount": "0.1", "jito_mode": {"tip_amount": "0.01", "priority_fee": "0.0005", "slippage": "15", "bundle_amount": ""}, "priority_mode": {"tip_amount": "0.001", "priority_fee": "0.005", "slippage": "15", "bundle_amount": ""}, "selected_dexes": ["pump-fun"], "dex_source": "global", "dev_min_amount": "", "dev_max_amount": "", "buy_once": false, "sol_slippage_mode": false, "snipe_external": false, "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, timestamps and request ID are illustrative:
{ "data": { "id": "3f2b8c1e-5d4a-4b7e-9c2f-1a6d8e0b4c37", "remark": "Primary Pump sniper", "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", "config": { "buy_amount": "0.1", "...": "stored config as sent" } }, "meta": { "request_id": "req_0123456789abcdef", "persistence": "committed", "propagation": "eventual" }}meta.persistence and meta.propagation are optional in the schema; when present, committed means the database write finished and eventual means execution nodes may need a short time to see it.
Get: GET /sniper/configs/{id}
Section titled “Get: GET /sniper/configs/{id}”id is a UUID. A missing ID and an ID owned by another user both return 404 not_found. The 200 response carries the ETag needed for the next write. List and get return the stored snapshot as it is, so older configurations may contain legacy web-UI fields that the current create schema would not accept.
Patch: PATCH /sniper/configs/{id}
Section titled “Patch: PATCH /sniper/configs/{id}”Body (SniperPatch): any non-empty subset of remark, risk_label_id, config. Rules:
- At least one top-level field must be present.
target_walletcannot be changed.- On an active configuration only
remarkandrisk_label_idmay change. Changingconfigrequires the configuration to be paused, otherwise the request fails (the error enum providesconfig_must_be_pausedfor this case). config, when present, replaces the whole stored config and must satisfy the full schema. Send the complete object, not a diff.risk_label_id: nullremoves the label.
The operation description also says to pause before changing linked_wallets, but SniperPatch has no linked_wallets property; that field exists only on copy-trade.
Pause and resume
Section titled “Pause and resume”POST /sniper/configs/{id}/pause sets the stored state to paused immediately; propagation to execution nodes is eventual. POST /sniper/configs/{id}/resume revalidates the complete stored config against the current schema before activating it. A stored config that no longer validates (for example legacy data) is not activated, and the request fails with a validation error. Neither call takes a body; both require If-Match and return the updated resource with a new ETag.
Example: resume
Section titled “Example: resume”curl --request POST 'https://x-trader.ai/api/v1/sniper/configs/3f2b8c1e-5d4a-4b7e-9c2f-1a6d8e0b4c37/resume' \ --header "Authorization: Bearer $X_TRADER_API_TOKEN" \ --header 'If-Match: "v1"'Response 200 OK (header ETag with the new version; values illustrative):
{ "data": { "id": "3f2b8c1e-5d4a-4b7e-9c2f-1a6d8e0b4c37", "remark": "Primary Pump sniper", "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", "config": { "buy_amount": "0.1", "...": "unchanged stored config" } }, "meta": { "request_id": "req_0123456789abcdef", "persistence": "committed", "propagation": "eventual" }}Read the version from the ETag header rather than computing it; the spec only states that version is a positive concurrency version.
Python: create then resume
Section titled “Python: create then resume”import osimport uuidimport requests
BASE = "https://x-trader.ai/api/v1"HEADERS = {"Authorization": f"Bearer {os.environ['X_TRADER_API_TOKEN']}"}
body = { "remark": "Primary Pump sniper", "target_wallet": "11111111111111111111111111111111", "config": { "buy_amount": "0.1", "jito_mode": {"tip_amount": "0.01", "priority_fee": "0.0005", "slippage": "15", "bundle_amount": ""}, "priority_mode": {"tip_amount": "0.001", "priority_fee": "0.005", "slippage": "15", "bundle_amount": ""}, "selected_dexes": ["pump-fun"], "dex_source": "global", "dev_min_amount": "", "dev_max_amount": "", "buy_once": False, "sol_slippage_mode": False, "snipe_external": False, "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": []}, },}
r = requests.post(f"{BASE}/sniper/configs", json=body, headers={**HEADERS, "Idempotency-Key": f"sniper-{uuid.uuid4()}"})r.raise_for_status()config_id = r.json()["data"]["id"]etag = r.headers["ETag"]
r = requests.post(f"{BASE}/sniper/configs/{config_id}/resume", headers={**HEADERS, "If-Match": etag})r.raise_for_status()print(r.json()["data"]["is_active"], r.headers["ETag"])Store the generated idempotency key with the request if you may need to retry it; a retry with the same key and the same body is safe.
Delete: DELETE /sniper/configs/{id}
Section titled “Delete: DELETE /sniper/configs/{id}”Requires sniper:delete and If-Match. Only paused configurations can be deleted; an active one is refused. Success is 204 with no body.
Lifecycle
Section titled “Lifecycle”POST /sniper/configscreates the task paused and returnsETag.- Inspect the returned resource, then
POST .../resumewith thatETag. - To change strategy:
POST .../pause, thenPATCHwith the fullconfigand the latestETag, thenPOST .../resume. remarkandrisk_label_idcan be patched without pausing, but still need the latestIf-Match.- To remove: pause, then
DELETE.
Every write returns a new ETag; always use the one from the latest response.
Response envelope and errors
Section titled “Response envelope and errors”Success bodies have data (one SniperResource, or items plus next_cursor for lists) and meta.request_id. SniperResource fields: id, remark, target_wallet, is_active, risk_label_id, version, created_at, updated_at, config.
Errors use { "error": { "code", "message", "fields"? }, "request_id" }, where fields lists up to 20 { path, reason } validation failures. Branch on error.code. Codes most relevant to this resource:
| Code | Typical cause on /sniper/configs |
|---|---|
validation_error |
Unknown property, JSON number instead of decimal string, out-of-range value, empty PATCH, failed revalidation on resume |
insufficient_scope |
Token lacks sniper:read, sniper:write or sniper:delete |
not_found |
Unknown ID, another user’s ID, or a foreign risk_label_id / label_id |
config_must_be_paused |
config PATCH or delete on an active configuration |
version_conflict |
If-Match is not the latest version; GET again before retrying |
idempotency_required / idempotency_conflict |
Missing Idempotency-Key, or key reused with a different body |
quota_exceeded |
Shared configuration quota reached |
rate_limit_exceeded, service_unavailable |
429 / 503, with Retry-After |
The spec lists the error codes and the HTTP statuses per operation but does not publish a code-to-status table. See /xtrader/errors/.
Why is my new sniper config not running after create?
Section titled “Why is my new sniper config not running after create?”Every create returns a configuration with is_active=false. Call POST /sniper/configs/{id}/resume with the ETag from the create response as If-Match to enable it.
Can I change the config of an active sniper?
Section titled “Can I change the config of an active sniper?”No. While a sniper is active only remark and risk_label_id can be patched. Pause it first, then PATCH config, then resume.
Can I change target_wallet after creation?
Section titled “Can I change target_wallet after creation?”No. target_wallet is immutable. Create a new configuration for a different address and delete the old one after pausing it.
Which scopes does the sniper API need?
Section titled “Which scopes does the sniper API need?”sniper:read for list and get, sniper:write for create, patch, pause and resume, and sniper:delete for delete.
What does a PATCH with config do to fields I leave out?
Section titled “What does a PATCH with config do to fields I leave out?”The config object in a PATCH replaces the stored config completely and must pass the full schema, so omitted nested fields are not merged from the old value.