> For the complete documentation index, see [llms.txt](https://lambdaplex-labs.gitbook.io/lambdaplex/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lambdaplex-labs.gitbook.io/lambdaplex/for-developers/api/bots.md).

# Bots

Caller-scoped bot automation resources

## Queue a durable withdrawal from the authenticated bot to its registered owner

> Requires an API key created with the immutable bot-withdrawal capability. Funds can move only\
> from the authenticated bot account to that bot's registered owner.\
> \
> A successful response means the request, immutable Hedera transaction identity, and balance\
> reservations are durable. It does not mean the transaction has already been submitted or\
> reached consensus; poll the returned \`\_links.self\` resource until \`terminal\` is \`true\`.\
> \
> The body must be strict JSON: unknown properties, duplicate keys, trailing JSON values,\
> numeric \`amount\` values, \`Content-Encoding\`, and request bodies larger than 16 KiB are\
> rejected. Send \`Content-Type: application/json\`, serialize the body once, hash those exact\
> transmitted bytes, and send no query string.\
> \
> \`X-API-KEY\` and \`X-PLEX-SIGNATURE\` are represented by this operation's security schemes.\
> The other four required signing headers are explicit operation parameters below.\
> \
> \
> \## Ed25519 V2 signing\
> \
> \`V2\` names the signing protocol, not the REST path version. Use it whenever an\
> operation's security requirement contains \`Ed25519V2Sig\`.\
> \
> 1\. Serialize the request body exactly once to the bytes that will be sent. Do not\
> &#x20;  compress it or reserialize it after signing. A bodyless request signs zero bytes.\
> 2\. Set \`X-PLEX-CONTENT-SHA256\` to lowercase hexadecimal SHA-256 of those exact bytes.\
> 3\. Build this canonical text, substituting the exact header and request values:\
> \
> \`\`\`text\
> LPX-ED25519-V2\
> \<exact X-API-KEY value>\
> \<uppercase HTTP method>\
> \<raw request path>\
> \<X-PLEX-TIMESTAMP decimal value>\
> \<X-PLEX-RECV-WINDOW decimal value>\
> \<X-PLEX-CONTENT-SHA256 lowercase value>\
> \`\`\`\
> \
> Every line separator is one LF byte (\`0x0A\`), including one final LF after the\
> content hash. There is no leading LF, CR byte, blank line, or surrounding whitespace.\
> Encode the canonical text as UTF-8, sign those bytes with the API key's Ed25519 private\
> key, and set \`X-PLEX-SIGNATURE\` to standard padded Base64 of the raw 64-byte signature.\
> \
> The raw path begins with \`/\` and is exactly the URL path sent on the wire. It excludes\
> scheme, authority, fragment, and query. Do not URL-decode, normalize, or re-encode it.\
> V2 requests must not contain a query string. Send exactly one value for each required\
> V2 header.\
> \
> Minimal Python reference, assuming \`private\_key\` is a loaded\
> \`cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey\`:\
> \
> \`\`\`python\
> import base64, hashlib, json, requests\
> \
> \# Define api\_key, private\_key, base\_url, and payload before this block.\
> method = "POST"\
> raw\_path = "/api/v1/bot/withdrawals"\
> timestamp = str(requests.get(base\_url + "/api/v1/time", timeout=30).json()\["serverTime"])\
> recv\_window = "5000"\
> body = json.dumps(payload, separators=(",", ":"), ensure\_ascii=False).encode("utf-8")\
> content\_hash = hashlib.sha256(body).hexdigest()\
> canonical = "\n".join(\[\
> &#x20;   "LPX-ED25519-V2", api\_key, method, raw\_path,\
> &#x20;   timestamp, recv\_window, content\_hash, ""\
> ]).encode("utf-8")\
> headers = {\
> &#x20;   "X-API-KEY": api\_key,\
> &#x20;   "X-PLEX-SIGNATURE-VERSION": "2",\
> &#x20;   "X-PLEX-TIMESTAMP": timestamp,\
> &#x20;   "X-PLEX-RECV-WINDOW": recv\_window,\
> &#x20;   "X-PLEX-CONTENT-SHA256": content\_hash,\
> &#x20;   "X-PLEX-SIGNATURE": base64.b64encode(private\_key.sign(canonical)).decode("ascii"),\
> &#x20;   "Content-Type": "application/json",\
> }\
> response = requests.request(method, base\_url + raw\_path, data=body, headers=headers)\
> \`\`\`\
> \
> For a bodyless \`GET\`, use \`body = b""\`, omit \`Content-Type\`, and substitute the complete\
> resource path before calculating the signature.\
> \
> \
> \## Deterministic interoperability vector\
> \
> The timestamp below is intentionally fixed and is only for testing a signer; a live\
> request must use a fresh server-compatible epoch-millisecond timestamp.\
> \
> \- API key: \`lp\_example\_bot\_key\`\
> \- Method: \`POST\`\
> \- Raw path: \`/api/v1/bot/withdrawals\`\
> \- Timestamp: \`1784832000123\`\
> \- Receive window: \`5000\`\
> \- Exact body length: \`174\` UTF-8 bytes, with no trailing newline\
> \- Exact body:\
> \
> \`\`\`json\
> {"clientWithdrawalId":"accountant-2026-07-23T20:00Z","transfers":\[{"asset":"HBAR","amount":"123.5"},{"asset":"USDC","amount":"400"},{"asset":"0.0.789012","amount":"0.0001"}]}\
> \`\`\`\
> \
> \- Expected body SHA-256:\
> &#x20; \`08b4bf5aa2a74824b03226f3277a248fc80d37acc8e1d5721464fba2a270cd02\`\
> \- Canonical UTF-8 text:\
> \
> \`\`\`text\
> LPX-ED25519-V2\
> lp\_example\_bot\_key\
> POST\
> /api/v1/bot/withdrawals\
> 1784832000123\
> 5000\
> 08b4bf5aa2a74824b03226f3277a248fc80d37acc8e1d5721464fba2a270cd02\
> \`\`\`\
> \
> The canonical text above has one final LF after the hash and is exactly 147 bytes.\
> \
> Test-only Ed25519 material—never register or fund this key:\
> \
> \- 32-byte private seed:\
> &#x20; \`9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60\`\
> \- Public key:\
> &#x20; \`d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a\`\
> \- Expected \`X-PLEX-SIGNATURE\`:\
> &#x20; \`XFXlsg5++OyHm81DXCYg785515Jz1ecHtO9fJiZNEr3RXvkzB9FDcQmA7Q/uRlCK6JUVIphKY5I+OrV3UswqDQ==\`\
> \
> \
> \*\*Weight:\*\* 100

````json
{"openapi":"3.1.0","info":{"title":"Lambdaplex API","version":"1.0"},"tags":[{"name":"Bots","description":"Caller-scoped bot automation resources"}],"servers":[{"url":"https://api.lambdaplex.io","description":"Generated server url"}],"security":[{"XApiKey":[],"Ed25519V2Sig":[]}],"components":{"securitySchemes":{"XApiKey":{"type":"apiKey","description":"API key identifier. For Ed25519 V2 operations, its exact header value is the second line of the canonical signing payload.","name":"X-API-KEY","in":"header"}},"schemas":{"BotWithdrawalCreateRequest":{"type":"object","description":"Idempotent request to transfer one or more assets from a bot to its registered owner","properties":{"clientWithdrawalId":{"type":"string","description":"Caller-generated idempotency key, unique per bot","maxLength":64,"minLength":1,"pattern":"^[A-Za-z0-9._:-]+$"},"transfers":{"type":"array","description":"One to ten distinct HBAR, unambiguous HTS symbol, or canonical HTS token transfers","items":{"$ref":"#/components/schemas/BotWithdrawalTransferRequest"},"maxItems":10,"minItems":1,"uniqueItems":true}},"required":["clientWithdrawalId","transfers"]},"BotWithdrawalTransferRequest":{"type":"object","properties":{"asset":{"type":"string","description":"HBAR, an unambiguous HTS token symbol, or canonical Hedera token ID","minLength":1},"amount":{"type":"string","description":"Positive exact decimal amount encoded as a JSON string; scientific notation and excess precision are rejected","pattern":"^(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?$"}},"required":["amount","asset"]},"BotWithdrawal":{"type":"object","properties":{"withdrawalId":{"type":"string"},"clientWithdrawalId":{"type":"string"},"status":{"type":"string","enum":["QUEUED","SUBMITTING","SUBMITTED","CONFIRMING","SUCCEEDED","FAILED"]},"terminal":{"type":"boolean"},"from":{"type":"string"},"to":{"type":"string"},"transfers":{"type":"array","items":{"$ref":"#/components/schemas/Transfer"}},"transaction":{"$ref":"#/components/schemas/Transaction"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"submittedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"failure":{"$ref":"#/components/schemas/Failure"},"_links":{"$ref":"#/components/schemas/Links"}}},"Transfer":{"type":"object","properties":{"asset":{"type":"string"},"symbol":{"type":"string"},"amount":{"type":"string"}}},"Transaction":{"type":"object","properties":{"transactionId":{"type":"string"},"nodeAccountId":{"type":"string"},"validStart":{"type":"string"},"precheckStatus":{"type":"string"},"receiptStatus":{"type":"string"},"consensusTimestamp":{"type":"string"}}},"Failure":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"Links":{"type":"object","properties":{"self":{"type":"string"}}},"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"RateLimitError":{"type":"object","description":"Error response returned when a request is rate limited (HTTP 429)","properties":{"code":{"type":"string","description":"Error code indicating rate limiting"},"status":{"type":"integer","format":"int32","description":"HTTP status code"},"scope":{"type":"string","description":"Scope of the rate limit: 'rest' for HTTP endpoints, 'ws' for WebSocket handshake","enum":["rest","ws"]},"bucket":{"type":"string","description":"Bucket that triggered the limit (e.g. 'rest_weight', 'orders', 'ws_handshake')"},"keyType":{"type":"string","description":"Key type that was rate limited: 'ip' or 'api'","enum":["ip","api"]},"retryAfterSec":{"type":"number","format":"double","description":"Seconds until enough tokens are available to retry this request"}},"required":["bucket","code","keyType","retryAfterSec","scope","status"]}}},"paths":{"/api/v1/bot/withdrawals":{"post":{"tags":["Bots"],"summary":"Queue a durable withdrawal from the authenticated bot to its registered owner","description":"Requires an API key created with the immutable bot-withdrawal capability. Funds can move only\nfrom the authenticated bot account to that bot's registered owner.\n\nA successful response means the request, immutable Hedera transaction identity, and balance\nreservations are durable. It does not mean the transaction has already been submitted or\nreached consensus; poll the returned `_links.self` resource until `terminal` is `true`.\n\nThe body must be strict JSON: unknown properties, duplicate keys, trailing JSON values,\nnumeric `amount` values, `Content-Encoding`, and request bodies larger than 16 KiB are\nrejected. Send `Content-Type: application/json`, serialize the body once, hash those exact\ntransmitted bytes, and send no query string.\n\n`X-API-KEY` and `X-PLEX-SIGNATURE` are represented by this operation's security schemes.\nThe other four required signing headers are explicit operation parameters below.\n\n\n## Ed25519 V2 signing\n\n`V2` names the signing protocol, not the REST path version. Use it whenever an\noperation's security requirement contains `Ed25519V2Sig`.\n\n1. Serialize the request body exactly once to the bytes that will be sent. Do not\n   compress it or reserialize it after signing. A bodyless request signs zero bytes.\n2. Set `X-PLEX-CONTENT-SHA256` to lowercase hexadecimal SHA-256 of those exact bytes.\n3. Build this canonical text, substituting the exact header and request values:\n\n```text\nLPX-ED25519-V2\n<exact X-API-KEY value>\n<uppercase HTTP method>\n<raw request path>\n<X-PLEX-TIMESTAMP decimal value>\n<X-PLEX-RECV-WINDOW decimal value>\n<X-PLEX-CONTENT-SHA256 lowercase value>\n```\n\nEvery line separator is one LF byte (`0x0A`), including one final LF after the\ncontent hash. There is no leading LF, CR byte, blank line, or surrounding whitespace.\nEncode the canonical text as UTF-8, sign those bytes with the API key's Ed25519 private\nkey, and set `X-PLEX-SIGNATURE` to standard padded Base64 of the raw 64-byte signature.\n\nThe raw path begins with `/` and is exactly the URL path sent on the wire. It excludes\nscheme, authority, fragment, and query. Do not URL-decode, normalize, or re-encode it.\nV2 requests must not contain a query string. Send exactly one value for each required\nV2 header.\n\nMinimal Python reference, assuming `private_key` is a loaded\n`cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`:\n\n```python\nimport base64, hashlib, json, requests\n\n# Define api_key, private_key, base_url, and payload before this block.\nmethod = \"POST\"\nraw_path = \"/api/v1/bot/withdrawals\"\ntimestamp = str(requests.get(base_url + \"/api/v1/time\", timeout=30).json()[\"serverTime\"])\nrecv_window = \"5000\"\nbody = json.dumps(payload, separators=(\",\", \":\"), ensure_ascii=False).encode(\"utf-8\")\ncontent_hash = hashlib.sha256(body).hexdigest()\ncanonical = \"\\n\".join([\n    \"LPX-ED25519-V2\", api_key, method, raw_path,\n    timestamp, recv_window, content_hash, \"\"\n]).encode(\"utf-8\")\nheaders = {\n    \"X-API-KEY\": api_key,\n    \"X-PLEX-SIGNATURE-VERSION\": \"2\",\n    \"X-PLEX-TIMESTAMP\": timestamp,\n    \"X-PLEX-RECV-WINDOW\": recv_window,\n    \"X-PLEX-CONTENT-SHA256\": content_hash,\n    \"X-PLEX-SIGNATURE\": base64.b64encode(private_key.sign(canonical)).decode(\"ascii\"),\n    \"Content-Type\": \"application/json\",\n}\nresponse = requests.request(method, base_url + raw_path, data=body, headers=headers)\n```\n\nFor a bodyless `GET`, use `body = b\"\"`, omit `Content-Type`, and substitute the complete\nresource path before calculating the signature.\n\n\n## Deterministic interoperability vector\n\nThe timestamp below is intentionally fixed and is only for testing a signer; a live\nrequest must use a fresh server-compatible epoch-millisecond timestamp.\n\n- API key: `lp_example_bot_key`\n- Method: `POST`\n- Raw path: `/api/v1/bot/withdrawals`\n- Timestamp: `1784832000123`\n- Receive window: `5000`\n- Exact body length: `174` UTF-8 bytes, with no trailing newline\n- Exact body:\n\n```json\n{\"clientWithdrawalId\":\"accountant-2026-07-23T20:00Z\",\"transfers\":[{\"asset\":\"HBAR\",\"amount\":\"123.5\"},{\"asset\":\"USDC\",\"amount\":\"400\"},{\"asset\":\"0.0.789012\",\"amount\":\"0.0001\"}]}\n```\n\n- Expected body SHA-256:\n  `08b4bf5aa2a74824b03226f3277a248fc80d37acc8e1d5721464fba2a270cd02`\n- Canonical UTF-8 text:\n\n```text\nLPX-ED25519-V2\nlp_example_bot_key\nPOST\n/api/v1/bot/withdrawals\n1784832000123\n5000\n08b4bf5aa2a74824b03226f3277a248fc80d37acc8e1d5721464fba2a270cd02\n```\n\nThe canonical text above has one final LF after the hash and is exactly 147 bytes.\n\nTest-only Ed25519 material—never register or fund this key:\n\n- 32-byte private seed:\n  `9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60`\n- Public key:\n  `d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a`\n- Expected `X-PLEX-SIGNATURE`:\n  `XFXlsg5++OyHm81DXCYg785515Jz1ecHtO9fJiZNEr3RXvkzB9FDcQmA7Q/uRlCK6JUVIphKY5I+OrV3UswqDQ==`\n\n\n**Weight:** 100","operationId":"createBotWithdrawal","parameters":[{"name":"X-PLEX-SIGNATURE-VERSION","in":"header","description":"Ed25519 signing protocol version. This operation accepts only the literal `2`.","required":true,"schema":{"type":"string","enum":["2"]}},{"name":"X-PLEX-TIMESTAMP","in":"header","description":"Fresh Unix epoch time in decimal milliseconds. Use `GET /api/v1/time` to avoid local-clock drift; sign this exact decimal text. The fixed example belongs only to the interoperability vector and is stale for live requests.","required":true,"schema":{"type":"string","pattern":"^[0-9]+$"}},{"name":"X-PLEX-RECV-WINDOW","in":"header","description":"Positive receive window in decimal milliseconds. `5000` is the current maximum; sign this exact decimal text.","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"X-PLEX-CONTENT-SHA256","in":"header","description":"Lowercase hexadecimal SHA-256 of the exact transmitted request-body bytes.","required":true,"schema":{"type":"string","maxLength":64,"minLength":64,"pattern":"^[0-9a-f]{64}$"}}],"requestBody":{"description":"Strict JSON encoded as UTF-8. Hash the exact serialized bytes sent on the wire; JSON\nwhitespace and property ordering are permitted but change the required content hash\nand signature. Keep `amount` as a JSON string. The example is the exact 174-byte,\nno-trailing-newline body used by the signing interoperability vector.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BotWithdrawalCreateRequest"}}},"required":true},"responses":{"200":{"description":"Exact idempotent replay of a terminal withdrawal","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BotWithdrawal"}}}},"202":{"description":"Withdrawal durably queued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BotWithdrawal"}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Missing or invalid V2 signing headers or signature","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Key lacks withdrawal capability","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"409":{"description":"Idempotency, active-slot, association, or funds conflict","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"413":{"description":"Request body exceeds the 16 KiB V2 limit","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"429":{"description":"Rolling withdrawal budget or general API-key rate limit exhausted","content":{"application/problem+json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/ProblemDetail"},{"$ref":"#/components/schemas/RateLimitError"}]}},"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}},"503":{"description":"Withdrawal intake or a required dependency is unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}}}}
````

## Get a bot withdrawal owned by the authenticated bot

> Returns the current durable state only when the withdrawal belongs to the authenticated bot.\
> Poll until \`terminal\` is \`true\`; terminal statuses are \`SUCCEEDED\` and \`FAILED\`.\
> \
> Send no request body, no \`Content-Type\`, and no query string. The exact body is therefore\
> zero bytes and \`X-PLEX-CONTENT-SHA256\` must be:\
> \`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\`.\
> \
> \`X-API-KEY\` and \`X-PLEX-SIGNATURE\` are represented by this operation's security schemes.\
> The other four required signing headers are explicit operation parameters below. Substitute\
> the complete raw path, including the \`withdrawalId\`, into the V2 canonical payload.\
> \
> \
> \## Ed25519 V2 signing\
> \
> \`V2\` names the signing protocol, not the REST path version. Use it whenever an\
> operation's security requirement contains \`Ed25519V2Sig\`.\
> \
> 1\. Serialize the request body exactly once to the bytes that will be sent. Do not\
> &#x20;  compress it or reserialize it after signing. A bodyless request signs zero bytes.\
> 2\. Set \`X-PLEX-CONTENT-SHA256\` to lowercase hexadecimal SHA-256 of those exact bytes.\
> 3\. Build this canonical text, substituting the exact header and request values:\
> \
> \`\`\`text\
> LPX-ED25519-V2\
> \<exact X-API-KEY value>\
> \<uppercase HTTP method>\
> \<raw request path>\
> \<X-PLEX-TIMESTAMP decimal value>\
> \<X-PLEX-RECV-WINDOW decimal value>\
> \<X-PLEX-CONTENT-SHA256 lowercase value>\
> \`\`\`\
> \
> Every line separator is one LF byte (\`0x0A\`), including one final LF after the\
> content hash. There is no leading LF, CR byte, blank line, or surrounding whitespace.\
> Encode the canonical text as UTF-8, sign those bytes with the API key's Ed25519 private\
> key, and set \`X-PLEX-SIGNATURE\` to standard padded Base64 of the raw 64-byte signature.\
> \
> The raw path begins with \`/\` and is exactly the URL path sent on the wire. It excludes\
> scheme, authority, fragment, and query. Do not URL-decode, normalize, or re-encode it.\
> V2 requests must not contain a query string. Send exactly one value for each required\
> V2 header.\
> \
> Minimal Python reference, assuming \`private\_key\` is a loaded\
> \`cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey\`:\
> \
> \`\`\`python\
> import base64, hashlib, json, requests\
> \
> \# Define api\_key, private\_key, base\_url, and payload before this block.\
> method = "POST"\
> raw\_path = "/api/v1/bot/withdrawals"\
> timestamp = str(requests.get(base\_url + "/api/v1/time", timeout=30).json()\["serverTime"])\
> recv\_window = "5000"\
> body = json.dumps(payload, separators=(",", ":"), ensure\_ascii=False).encode("utf-8")\
> content\_hash = hashlib.sha256(body).hexdigest()\
> canonical = "\n".join(\[\
> &#x20;   "LPX-ED25519-V2", api\_key, method, raw\_path,\
> &#x20;   timestamp, recv\_window, content\_hash, ""\
> ]).encode("utf-8")\
> headers = {\
> &#x20;   "X-API-KEY": api\_key,\
> &#x20;   "X-PLEX-SIGNATURE-VERSION": "2",\
> &#x20;   "X-PLEX-TIMESTAMP": timestamp,\
> &#x20;   "X-PLEX-RECV-WINDOW": recv\_window,\
> &#x20;   "X-PLEX-CONTENT-SHA256": content\_hash,\
> &#x20;   "X-PLEX-SIGNATURE": base64.b64encode(private\_key.sign(canonical)).decode("ascii"),\
> &#x20;   "Content-Type": "application/json",\
> }\
> response = requests.request(method, base\_url + raw\_path, data=body, headers=headers)\
> \`\`\`\
> \
> For a bodyless \`GET\`, use \`body = b""\`, omit \`Content-Type\`, and substitute the complete\
> resource path before calculating the signature.\
> \
> \
> \*\*Weight:\*\* 2

````json
{"openapi":"3.1.0","info":{"title":"Lambdaplex API","version":"1.0"},"tags":[{"name":"Bots","description":"Caller-scoped bot automation resources"}],"servers":[{"url":"https://api.lambdaplex.io","description":"Generated server url"}],"security":[{"XApiKey":[],"Ed25519V2Sig":[]}],"components":{"securitySchemes":{"XApiKey":{"type":"apiKey","description":"API key identifier. For Ed25519 V2 operations, its exact header value is the second line of the canonical signing payload.","name":"X-API-KEY","in":"header"}},"schemas":{"BotWithdrawal":{"type":"object","properties":{"withdrawalId":{"type":"string"},"clientWithdrawalId":{"type":"string"},"status":{"type":"string","enum":["QUEUED","SUBMITTING","SUBMITTED","CONFIRMING","SUCCEEDED","FAILED"]},"terminal":{"type":"boolean"},"from":{"type":"string"},"to":{"type":"string"},"transfers":{"type":"array","items":{"$ref":"#/components/schemas/Transfer"}},"transaction":{"$ref":"#/components/schemas/Transaction"},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"},"submittedAt":{"type":"string","format":"date-time"},"completedAt":{"type":"string","format":"date-time"},"failure":{"$ref":"#/components/schemas/Failure"},"_links":{"$ref":"#/components/schemas/Links"}}},"Transfer":{"type":"object","properties":{"asset":{"type":"string"},"symbol":{"type":"string"},"amount":{"type":"string"}}},"Transaction":{"type":"object","properties":{"transactionId":{"type":"string"},"nodeAccountId":{"type":"string"},"validStart":{"type":"string"},"precheckStatus":{"type":"string"},"receiptStatus":{"type":"string"},"consensusTimestamp":{"type":"string"}}},"Failure":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"}}},"Links":{"type":"object","properties":{"self":{"type":"string"}}},"ProblemDetail":{"type":"object","properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer","format":"int32"},"detail":{"type":"string"},"instance":{"type":"string","format":"uri"},"properties":{"type":"object","additionalProperties":{}}}},"RateLimitError":{"type":"object","description":"Error response returned when a request is rate limited (HTTP 429)","properties":{"code":{"type":"string","description":"Error code indicating rate limiting"},"status":{"type":"integer","format":"int32","description":"HTTP status code"},"scope":{"type":"string","description":"Scope of the rate limit: 'rest' for HTTP endpoints, 'ws' for WebSocket handshake","enum":["rest","ws"]},"bucket":{"type":"string","description":"Bucket that triggered the limit (e.g. 'rest_weight', 'orders', 'ws_handshake')"},"keyType":{"type":"string","description":"Key type that was rate limited: 'ip' or 'api'","enum":["ip","api"]},"retryAfterSec":{"type":"number","format":"double","description":"Seconds until enough tokens are available to retry this request"}},"required":["bucket","code","keyType","retryAfterSec","scope","status"]}},"responses":{"RateLimited":{"description":"Too Many Requests - Rate limit exceeded.","headers":{"X-PLEX-USED-WEIGHT-API":{"description":"Current used weight for your API key this minute (if authenticated)","schema":{}},"Retry-After":{"description":"Number of seconds to wait before retrying","schema":{}},"X-PLEX-ORDER-COUNT":{"description":"Current order count for your API key (for order endpoints)","schema":{}},"X-PLEX-USED-WEIGHT-IP":{"description":"Current used weight for your IP this minute","schema":{}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}},"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}}}}},"paths":{"/api/v1/bot/withdrawals/{withdrawalId}":{"get":{"tags":["Bots"],"summary":"Get a bot withdrawal owned by the authenticated bot","description":"Returns the current durable state only when the withdrawal belongs to the authenticated bot.\nPoll until `terminal` is `true`; terminal statuses are `SUCCEEDED` and `FAILED`.\n\nSend no request body, no `Content-Type`, and no query string. The exact body is therefore\nzero bytes and `X-PLEX-CONTENT-SHA256` must be:\n`e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.\n\n`X-API-KEY` and `X-PLEX-SIGNATURE` are represented by this operation's security schemes.\nThe other four required signing headers are explicit operation parameters below. Substitute\nthe complete raw path, including the `withdrawalId`, into the V2 canonical payload.\n\n\n## Ed25519 V2 signing\n\n`V2` names the signing protocol, not the REST path version. Use it whenever an\noperation's security requirement contains `Ed25519V2Sig`.\n\n1. Serialize the request body exactly once to the bytes that will be sent. Do not\n   compress it or reserialize it after signing. A bodyless request signs zero bytes.\n2. Set `X-PLEX-CONTENT-SHA256` to lowercase hexadecimal SHA-256 of those exact bytes.\n3. Build this canonical text, substituting the exact header and request values:\n\n```text\nLPX-ED25519-V2\n<exact X-API-KEY value>\n<uppercase HTTP method>\n<raw request path>\n<X-PLEX-TIMESTAMP decimal value>\n<X-PLEX-RECV-WINDOW decimal value>\n<X-PLEX-CONTENT-SHA256 lowercase value>\n```\n\nEvery line separator is one LF byte (`0x0A`), including one final LF after the\ncontent hash. There is no leading LF, CR byte, blank line, or surrounding whitespace.\nEncode the canonical text as UTF-8, sign those bytes with the API key's Ed25519 private\nkey, and set `X-PLEX-SIGNATURE` to standard padded Base64 of the raw 64-byte signature.\n\nThe raw path begins with `/` and is exactly the URL path sent on the wire. It excludes\nscheme, authority, fragment, and query. Do not URL-decode, normalize, or re-encode it.\nV2 requests must not contain a query string. Send exactly one value for each required\nV2 header.\n\nMinimal Python reference, assuming `private_key` is a loaded\n`cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey`:\n\n```python\nimport base64, hashlib, json, requests\n\n# Define api_key, private_key, base_url, and payload before this block.\nmethod = \"POST\"\nraw_path = \"/api/v1/bot/withdrawals\"\ntimestamp = str(requests.get(base_url + \"/api/v1/time\", timeout=30).json()[\"serverTime\"])\nrecv_window = \"5000\"\nbody = json.dumps(payload, separators=(\",\", \":\"), ensure_ascii=False).encode(\"utf-8\")\ncontent_hash = hashlib.sha256(body).hexdigest()\ncanonical = \"\\n\".join([\n    \"LPX-ED25519-V2\", api_key, method, raw_path,\n    timestamp, recv_window, content_hash, \"\"\n]).encode(\"utf-8\")\nheaders = {\n    \"X-API-KEY\": api_key,\n    \"X-PLEX-SIGNATURE-VERSION\": \"2\",\n    \"X-PLEX-TIMESTAMP\": timestamp,\n    \"X-PLEX-RECV-WINDOW\": recv_window,\n    \"X-PLEX-CONTENT-SHA256\": content_hash,\n    \"X-PLEX-SIGNATURE\": base64.b64encode(private_key.sign(canonical)).decode(\"ascii\"),\n    \"Content-Type\": \"application/json\",\n}\nresponse = requests.request(method, base_url + raw_path, data=body, headers=headers)\n```\n\nFor a bodyless `GET`, use `body = b\"\"`, omit `Content-Type`, and substitute the complete\nresource path before calculating the signature.\n\n\n**Weight:** 2","operationId":"getBotWithdrawal","parameters":[{"name":"X-PLEX-SIGNATURE-VERSION","in":"header","description":"Ed25519 signing protocol version. This operation accepts only the literal `2`.","required":true,"schema":{"type":"string","enum":["2"]}},{"name":"X-PLEX-TIMESTAMP","in":"header","description":"Fresh Unix epoch time in decimal milliseconds. Use `GET /api/v1/time` to avoid local-clock drift; sign this exact decimal text. The fixed example belongs only to the interoperability vector and is stale for live requests.","required":true,"schema":{"type":"string","pattern":"^[0-9]+$"}},{"name":"X-PLEX-RECV-WINDOW","in":"header","description":"Positive receive window in decimal milliseconds. `5000` is the current maximum; sign this exact decimal text.","required":true,"schema":{"type":"string","pattern":"^[1-9][0-9]*$"}},{"name":"X-PLEX-CONTENT-SHA256","in":"header","description":"Lowercase hexadecimal SHA-256 of the zero-length request body.","required":true,"schema":{"type":"string","maxLength":64,"minLength":64,"pattern":"^[0-9a-f]{64}$"}},{"name":"withdrawalId","in":"path","description":"Opaque server-assigned withdrawal ID returned by the create operation","required":true,"schema":{"type":"string","maxLength":25,"minLength":25,"pattern":"^wd_[A-Za-z0-9_-]{22}$"}}],"responses":{"200":{"description":"Current durable withdrawal state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BotWithdrawal"}}}},"400":{"description":"A body was supplied to the status request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"401":{"description":"Missing or invalid V2 signing headers or signature","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"403":{"description":"Key lacks withdrawal capability","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"404":{"description":"Unknown or foreign withdrawal ID","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}},"429":{"$ref":"#/components/responses/RateLimited"},"503":{"description":"Service Unavailable","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/ProblemDetail"}}}}}}}}}
````
