Hands in your policy key’s signature over a draft’s bytes. Citadel never signs a policy
document: your engine installs only what your policy key (key B) signed. Citadel checks the
signature against the policy key your engine registered when it joined.
The draft does not go to your engine yet. It becomes pending, and an approval request
opens in the console with the bar of your approval rules in force: as many approvals as
their strictest rule asks, from that rule’s group. When no approval rules are in force, or
they hold nothing, one member of your organization approves. Approvers confirm with their
passkeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (published), which
verifies the signature again and installs it. Declined or not approved in time, it is
rejected and never sent.
A newer change replaces one still waiting, and so does a newer version published in the
console: the older one reads rejected, rejected_reason: superseded. A rejected version
number is never used again.
Sending the same version and signature again answers the change as it stands, so a retry
after a lost answer is safe. No network. Needs the policy.write scope. A key limited to
vaults is refused with 403 vault_limited.
An API key minted in the console (ctd_…). Authenticates; never authorizes funds. A key
carries scopes (read, wallets.write, transactions.write, events.write,
webhooks.write, policy.write) and may be limited to vaults, to IP addresses, and in
lifetime — see Authentication.
Why a rejected change was never sent: an approver declined it, it was not approved in time, or a newer change replaced it or a newer version came into force first.
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Citadel API","colors":{"primary":"#1F5BD8","light":"#5B8BEF","dark":"#163F99"},"api":{"playground":{"display":"interactive"},"examples":{"languages":["curl","javascript","python","go"]}},"integrations":{"telemetry":{"enabled":false}},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"hasAudioNarration":false,"customDomains":[],"gitSource":{"type":"github","owner":"local","repo":"local","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":"local","isBase":true},"buildId":"local","clientVersion":"0.0.4088"},"basePath":"","hasChatPermissions":false,"hasWebMcpPermissions":false,"toggles":[],"googleFontsInlined":false,"isLoggedInCli":false,"mintExport":{}},"customCss":[],"page":{"pagePath":"api-reference/policy/publish-a-signed-policy-draft","slug":"/api-reference/policy/publish-a-signed-policy-draft","pageMetadata":{"openapi":"/openapi.yaml post /policy/publish","description":"Hands in your policy key's signature over a draft's bytes. Citadel never signs a policy\ndocument: your engine installs only what your policy key (key B) signed. Citadel checks the\nsignature against the policy key your engine registered when it joined.\n\nThe draft does not go to your engine yet. It becomes `pending`, and an approval request\nopens in the console with the bar of your approval rules in force: as many approvals as\ntheir strictest rule asks, from that rule's group. When no approval rules are in force, or\nthey hold nothing, one member of your organization approves. Approvers confirm with their\npasskeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (`published`), which\nverifies the signature again and installs it. Declined or not approved in time, it is\n`rejected` and never sent.\n\nA newer change replaces one still waiting, and so does a newer version published in the\nconsole: the older one reads `rejected`, `rejected_reason: superseded`. A rejected version\nnumber is never used again.\n\nSending the same `version` and `signature` again answers the change as it stands, so a retry\nafter a lost answer is safe. No `network`. Needs the `policy.write` scope. A key limited to\nvaults is refused with `403 vault_limited`.\n","href":"/api-reference/policy/publish-a-signed-policy-draft","title":"Publish a signed policy draft"},"description":{"compiledSource":"\"use strict\";\nconst {Fragment: _Fragment, jsx: _jsx, jsxs: _jsxs} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n code: \"code\",\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsxs(_Fragment, {\n children: [_jsx(_components.p, {\n children: \"Hands in your policy key’s signature over a draft’s bytes. Citadel never signs a policy\\ndocument: your engine installs only what your policy key (key B) signed. Citadel checks the\\nsignature against the policy key your engine registered when it joined.\"\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"The draft does not go to your engine yet. It becomes \", _jsx(_components.code, {\n children: \"pending\"\n }), \", and an approval request\\nopens in the console with the bar of your approval rules in force: as many approvals as\\ntheir strictest rule asks, from that rule’s group. When no approval rules are in force, or\\nthey hold nothing, one member of your organization approves. Approvers confirm with their\\npasskeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (\", _jsx(_components.code, {\n children: \"published\"\n }), \"), which\\nverifies the signature again and installs it. Declined or not approved in time, it is\\n\", _jsx(_components.code, {\n children: \"rejected\"\n }), \" and never sent.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"A newer change replaces one still waiting, and so does a newer version published in the\\nconsole: the older one reads \", _jsx(_components.code, {\n children: \"rejected\"\n }), \", \", _jsx(_components.code, {\n children: \"rejected_reason: superseded\"\n }), \". A rejected version\\nnumber is never used again.\"]\n }), \"\\n\", _jsxs(_components.p, {\n children: [\"Sending the same \", _jsx(_components.code, {\n children: \"version\"\n }), \" and \", _jsx(_components.code, {\n children: \"signature\"\n }), \" again answers the change as it stands, so a retry\\nafter a lost answer is safe. No \", _jsx(_components.code, {\n children: \"network\"\n }), \". Needs the \", _jsx(_components.code, {\n children: \"policy.write\"\n }), \" scope. A key limited to\\nvaults is refused with \", _jsx(_components.code, {\n children: \"403 vault_limited\"\n }), \".\"]\n })]\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){return _jsx(_Fragment,{})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"openapi":"/openapi.yaml post /policy/publish","description":"Hands in your policy key's signature over a draft's bytes. Citadel never signs a policy\ndocument: your engine installs only what your policy key (key B) signed. Citadel checks the\nsignature against the policy key your engine registered when it joined.\n\nThe draft does not go to your engine yet. It becomes `pending`, and an approval request\nopens in the console with the bar of your approval rules in force: as many approvals as\ntheir strictest rule asks, from that rule's group. When no approval rules are in force, or\nthey hold nothing, one member of your organization approves. Approvers confirm with their\npasskeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (`published`), which\nverifies the signature again and installs it. Declined or not approved in time, it is\n`rejected` and never sent.\n\nA newer change replaces one still waiting, and so does a newer version published in the\nconsole: the older one reads `rejected`, `rejected_reason: superseded`. A rejected version\nnumber is never used again.\n\nSending the same `version` and `signature` again answers the change as it stands, so a retry\nafter a lost answer is safe. No `network`. Needs the `policy.write` scope. A key limited to\nvaults is refused with `403 vault_limited`.\n","href":"/api-reference/policy/publish-a-signed-policy-draft","title":"Publish a signed policy draft"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[],"codeExamples":{}},"jsLinks":[],"openApiReferenceData":{"operation":{"tags":["Policy"],"operationId":"publishPolicy","summary":"Publish a signed policy draft","description":"Hands in your policy key's signature over a draft's bytes. Citadel never signs a policy\ndocument: your engine installs only what your policy key (key B) signed. Citadel checks the\nsignature against the policy key your engine registered when it joined.\n\nThe draft does not go to your engine yet. It becomes `pending`, and an approval request\nopens in the console with the bar of your approval rules in force: as many approvals as\ntheir strictest rule asks, from that rule's group. When no approval rules are in force, or\nthey hold nothing, one member of your organization approves. Approvers confirm with their\npasskeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (`published`), which\nverifies the signature again and installs it. Declined or not approved in time, it is\n`rejected` and never sent.\n\nA newer change replaces one still waiting, and so does a newer version published in the\nconsole: the older one reads `rejected`, `rejected_reason: superseded`. A rejected version\nnumber is never used again.\n\nSending the same `version` and `signature` again answers the change as it stands, so a retry\nafter a lost answer is safe. No `network`. Needs the `policy.write` scope. A key limited to\nvaults is refused with `403 vault_limited`.\n","requestBody":"aaa0b459-9288-45b3-8bd8-daa98b81efe7","responses":{"202":"e34bc617-24e6-4ea8-8f50-27572371fe92","401":"34b2d75c-fe72-4995-9f08-aaa7942216c8","403":"47fb5361-f392-40df-ab0d-d76c9f1c582a","404":"7b070de5-61f5-441e-96b0-e7f5b90f7b33","409":"125c1aec-334d-4b18-8a0a-c818e88a3333","422":"ae3fe802-1e36-4578-b236-f68f93ed9b69"},"method":"post","type":"path","servers":["825d9789-7332-4a65-b039-b330384d4003"],"security":["24aebd70-fdbd-4dae-a4a9-d627e2d2711f"],"path":"/policy/publish","baseUrlOptions":["https://api.citadel.example/api/v1"],"title":"Publish a signed policy draft","codeSamples":[],"requestExampleLanguages":["bash","javascript","python","go"],"requiredOnlyExamples":false,"requestExampleType":"generated","responseExampleType":"generated"},"dependencies":{"parameters":{},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["version","signature"],"properties":{"version":{"type":"integer","description":"The draft's version, as `POST /policy/drafts` answered it.","isRequired":true,"uniqueKey":"version","typeLabel":"integer"},"signature":{"type":"string","description":"Your policy key's Ed25519 signature over `document`, exactly as answered: 64 bytes, in hex or standard base64.","isRequired":true,"uniqueKey":"signature","typeLabel":"string"}},"uniqueKey":"","typeLabel":"object"},"example":{"version":4,"signature":"dQhrBoRLDdj261cqTZkE1y3lLxq4fkEp/QrHPyFqMhSgkP4/iVx/xghGu3ExStse1SqOK/JMhlDvitcG0WQhBA=="},"examples":{"Example":{"title":"Example","value":{"version":4,"signature":"dQhrBoRLDdj261cqTZkE1y3lLxq4fkEp/QrHPyFqMhSgkP4/iVx/xghGu3ExStse1SqOK/JMhlDvitcG0WQhBA=="}}},"exampleType":"examples"}},"exampleType":"examples"},"responses":{"e34bc617-24e6-4ea8-8f50-27572371fe92":{"description":"Signed and waiting for approval.","content":{"application/json":{"schema":{"type":"object","required":["version","status","document","key_id","signature","approval","rejected_reason","created_at","updated_at"],"properties":{"version":{"type":"integer","isRequired":true,"uniqueKey":"version","typeLabel":"integer"},"status":{"type":"string","enum":["draft","pending","published","rejected"],"description":"`draft` — to sign and publish; `pending` — signed, waits for approval; `published` — sent to your engine; `rejected` — never sent.","isRequired":true,"uniqueKey":"status","typeLabel":"enum\u003cstring>"},"document":{"type":"string","description":"The YAML document: the exact bytes your policy key signs.","isRequired":true,"uniqueKey":"document","typeLabel":"string"},"key_id":{"oneOf":[{"type":"string","description":"The policy key that signed. `null` for a draft.","isRequired":true,"uniqueKey":"key_id.oneOf.0","typeLabel":"string"},{"type":"null","description":"The policy key that signed. `null` for a draft.","isRequired":true,"uniqueKey":"key_id.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"key_id","typeLabel":"string | null"},"signature":{"oneOf":[{"type":"string","description":"Your policy key's signature over `document`, in standard base64. `null` for a draft.","isRequired":true,"uniqueKey":"signature.oneOf.0","typeLabel":"string"},{"type":"null","description":"Your policy key's signature over `document`, in standard base64. `null` for a draft.","isRequired":true,"uniqueKey":"signature.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"signature","typeLabel":"string | null"},"approval":{"oneOf":[{"type":"null","description":"The approval a change published through the API waits for, or waited for. `null` for a draft, and for a version published in the console.","isRequired":true,"uniqueKey":"approval.oneOf.0","typeLabel":"null"},{"type":"object","description":"The approval a change published through the API waits for, or waited for. `null` for a draft, and for a version published in the console.","required":["id","status","required","approved","expires_at","decided_at"],"properties":{"id":{"type":"string","format":"uuid","isRequired":true,"uniqueKey":"approval.oneOf.1.id","typeLabel":"string\u003cuuid>"},"status":{"type":"string","enum":["pending","approved","rejected","expired","superseded"],"isRequired":true,"uniqueKey":"approval.oneOf.1.status","typeLabel":"enum\u003cstring>"},"required":{"type":"integer","description":"Approvals needed.","isRequired":true,"uniqueKey":"approval.oneOf.1.required","typeLabel":"integer"},"approved":{"type":"integer","description":"Approvals given so far.","isRequired":true,"uniqueKey":"approval.oneOf.1.approved","typeLabel":"integer"},"expires_at":{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"approval.oneOf.1.expires_at","typeLabel":"string\u003cdate-time>"},"decided_at":{"oneOf":[{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"approval.oneOf.1.decided_at.oneOf.0","typeLabel":"string\u003cdate-time>"},{"type":"null","format":"date-time","isRequired":true,"uniqueKey":"approval.oneOf.1.decided_at.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"approval.oneOf.1.decided_at","typeLabel":"string\u003cdate-time> | null"}},"isRequired":true,"uniqueKey":"approval.oneOf.1","typeLabel":"object"}],"isRequired":true,"uniqueKey":"approval","typeLabel":"null | object"},"rejected_reason":{"oneOf":[{"type":"string","enum":["approval_rejected","approval_expired","superseded",null],"description":"Why a `rejected` change was never sent: an approver declined it, it was not approved in time, or a newer change replaced it or a newer version came into force first.","isRequired":true,"uniqueKey":"rejected_reason.oneOf.0","typeLabel":"enum\u003cstring>"},{"type":"null","enum":["approval_rejected","approval_expired","superseded",null],"description":"Why a `rejected` change was never sent: an approver declined it, it was not approved in time, or a newer change replaced it or a newer version came into force first.","isRequired":true,"uniqueKey":"rejected_reason.oneOf.1","typeLabel":"null"}],"isRequired":true,"uniqueKey":"rejected_reason","typeLabel":"enum\u003cstring> | null"},"created_at":{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"created_at","typeLabel":"string\u003cdate-time>"},"updated_at":{"type":"string","format":"date-time","isRequired":true,"uniqueKey":"updated_at","typeLabel":"string\u003cdate-time>"}},"uniqueKey":"","typeLabel":"object"},"example":{"version":4,"status":"pending","document":"---\nceiling:\n TRX/6: '100000000'\n USDT/6: '50000000'\nchains:\n tron:\n assets:\n TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t: USDT/6\n native: TRX/6\n chain_module_version: 1\n wallets: {}\n whitelist:\n - TGz2TZmabrZZowj7G8BbyMYg6HacJLuzaj\n - TVsjquD89kcRu2WTSWsoJhYMNLQbFt8xQ4\nreferences: {}\nversion: 4\n","key_id":"merchant-policy-admin","signature":"dQhrBoRLDdj261cqTZkE1y3lLxq4fkEp/QrHPyFqMhSgkP4/iVx/xghGu3ExStse1SqOK/JMhlDvitcG0WQhBA==","approval":{"id":"0199f1a2-4b6c-7d8e-9f01-23456789abcd","status":"pending","required":2,"approved":0,"expires_at":"2026-10-18T13:00:00Z","decided_at":null},"rejected_reason":null,"created_at":"2026-10-18T09:00:00Z","updated_at":"2026-10-18T09:01:00Z"},"examples":{"Example":{"title":"Example","value":{"version":4,"status":"pending","document":"---\nceiling:\n TRX/6: '100000000'\n USDT/6: '50000000'\nchains:\n tron:\n assets:\n TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t: USDT/6\n native: TRX/6\n chain_module_version: 1\n wallets: {}\n whitelist:\n - TGz2TZmabrZZowj7G8BbyMYg6HacJLuzaj\n - TVsjquD89kcRu2WTSWsoJhYMNLQbFt8xQ4\nreferences: {}\nversion: 4\n","key_id":"merchant-policy-admin","signature":"dQhrBoRLDdj261cqTZkE1y3lLxq4fkEp/QrHPyFqMhSgkP4/iVx/xghGu3ExStse1SqOK/JMhlDvitcG0WQhBA==","approval":{"id":"0199f1a2-4b6c-7d8e-9f01-23456789abcd","status":"pending","required":2,"approved":0,"expires_at":"2026-10-18T13:00:00Z","decided_at":null},"rejected_reason":null,"created_at":"2026-10-18T09:00:00Z","updated_at":"2026-10-18T09:01:00Z"}}},"exampleType":"examples"}},"exampleType":"examples"},"34b2d75c-fe72-4995-9f08-aaa7942216c8":{"description":"Missing, unknown, revoked or expired API key.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable; not stable.","isRequired":true,"uniqueKey":"error","typeLabel":"string"},"code":{"type":"string","description":"Stable machine code, where one exists.","uniqueKey":"code","typeLabel":"string"},"retryable":{"type":"boolean","uniqueKey":"retryable","typeLabel":"boolean"}},"uniqueKey":"","typeLabel":"object"},"example":{"error":"unauthorized"},"examples":{"Example":{"title":"Example","value":{"error":"unauthorized"}}},"exampleType":"examples"}},"exampleType":"examples"},"47fb5361-f392-40df-ab0d-d76c9f1c582a":{"description":"The key may not make this call. Nothing was created or reserved. `code`:\n`ip_not_allowed` — the call came from an address outside the key's IP allowlist;\n`scope_missing` — the key lacks the scope the endpoint needs;\n`vault_limited` — the key is limited to vaults and the wallet is outside them, or the\nendpoint drives or lists workflows, manages webhook endpoints, reads webhook deliveries,\nreads or changes the policy document, or asks for or reads a report.\n","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable; not stable.","isRequired":true,"uniqueKey":"error","typeLabel":"string"},"code":{"type":"string","description":"Stable machine code, where one exists.","uniqueKey":"code","typeLabel":"string"},"retryable":{"type":"boolean","uniqueKey":"retryable","typeLabel":"boolean"}},"uniqueKey":"","typeLabel":"object"},"examples":{"ip_not_allowed":{"title":"ip_not_allowed","value":{"error":"request from 198.51.100.1 is not allowed for this key","code":"ip_not_allowed"}},"scope_missing":{"title":"scope_missing","value":{"error":"this key lacks the transactions.write scope","code":"scope_missing"}},"vault_limited":{"title":"vault_limited","value":{"error":"wallet is outside this key's vaults","code":"vault_limited"}}},"exampleType":"examples"}},"exampleType":"examples"},"7b070de5-61f5-441e-96b0-e7f5b90f7b33":{"description":"No policy document of this version. Draft one with `POST /policy/drafts`.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable; not stable.","isRequired":true,"uniqueKey":"error","typeLabel":"string"},"code":{"type":"string","description":"Stable machine code, where one exists.","uniqueKey":"code","typeLabel":"string"},"retryable":{"type":"boolean","uniqueKey":"retryable","typeLabel":"boolean"}},"uniqueKey":"","typeLabel":"object"},"example":{"error":"no policy document v7: draft one with POST /policy/drafts"},"examples":{"Example":{"title":"Example","value":{"error":"no policy document v7: draft one with POST /policy/drafts"}}},"exampleType":"examples"}},"exampleType":"examples"},"125c1aec-334d-4b18-8a0a-c818e88a3333":{"description":"The draft cannot be published. Nothing changed. `code`:\n`not_a_draft` — this version is signed already (pending, published or rejected), with\nanother signature; draft a new version;\n`policy_key_missing` — no policy key is registered for your organization: your policy\nengine registers it when it joins.\n","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable; not stable.","isRequired":true,"uniqueKey":"error","typeLabel":"string"},"code":{"type":"string","description":"Stable machine code, where one exists.","uniqueKey":"code","typeLabel":"string"},"retryable":{"type":"boolean","uniqueKey":"retryable","typeLabel":"boolean"}},"uniqueKey":"","typeLabel":"object"},"examples":{"not_a_draft":{"title":"not_a_draft","value":{"error":"v4 is pending already, with another signature: draft a new version to change the policy","code":"not_a_draft"}},"policy_key_missing":{"title":"policy_key_missing","value":{"error":"no policy key is registered for this organization: your policy engine registers it when it joins","code":"policy_key_missing"}}},"exampleType":"examples"}},"exampleType":"examples"},"ae3fe802-1e36-4578-b236-f68f93ed9b69":{"description":"The signature was refused. Nothing changed. `code`:\n`invalid_signature` — not a 64-byte Ed25519 signature in hex or base64;\n`signature_mismatch` — it does not verify over the draft's bytes with your policy key:\nother bytes were signed (the draft changed, or `document` was altered before signing)\nor another key signed. Without a `code`: `version` is not an integer.\n","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Human-readable; not stable.","isRequired":true,"uniqueKey":"error","typeLabel":"string"},"code":{"type":"string","description":"Stable machine code, where one exists.","uniqueKey":"code","typeLabel":"string"},"retryable":{"type":"boolean","uniqueKey":"retryable","typeLabel":"boolean"}},"uniqueKey":"","typeLabel":"object"},"examples":{"invalid_signature":{"title":"invalid_signature","value":{"error":"signature must be the 64-byte Ed25519 signature over the draft's bytes, in hex or standard base64","code":"invalid_signature"}},"signature_mismatch":{"title":"signature_mismatch","value":{"error":"the signature does not verify over the bytes of draft v4 with your policy key: sign `document` exactly as answered (GET /policy/drafts/4 reads it again)","code":"signature_mismatch"}}},"exampleType":"examples"}},"exampleType":"examples"}},"security":{"24aebd70-fdbd-4dae-a4a9-d627e2d2711f":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key minted in the console (`ctd_…`). Authenticates; never authorizes funds. A key\ncarries scopes (`read`, `wallets.write`, `transactions.write`, `events.write`,\n`webhooks.write`, `policy.write`) and may be limited to vaults, to IP addresses, and in\nlifetime — see Authentication.\n","scopes":[],"schemeName":"bearerAuth"}}},"servers":{"825d9789-7332-4a65-b039-b330384d4003":{"url":"https://api.citadel.example/api/v1"}},"processedSecurityOptions":[{"schemes":[{"scheme":{"type":"http","scheme":"bearer","description":"An API key minted in the console (`ctd_…`). Authenticates; never authorizes funds. A key\ncarries scopes (`read`, `wallets.write`, `transactions.write`, `events.write`,\n`webhooks.write`, `policy.write`) and may be limited to vaults, to IP addresses, and in\nlifetime — see Authentication.\n","scopes":[],"schemeName":"bearerAuth"},"schema":{"title":"Bearer Auth","type":"string","isRequired":true,"description":"An API key minted in the console (`ctd_…`). Authenticates; never authorizes funds. A key\ncarries scopes (`read`, `wallets.write`, `transactions.write`, `events.write`,\n`webhooks.write`, `policy.write`) and may be limited to vaults, to IP addresses, and in\nlifetime — see Authentication.\n","format":"bearer","typeLabel":"string\u003cbearer>","deprecated":false},"schemeName":"bearerAuth","name":"Authorization"}],"title":"bearerAuth"}]},"originalFileLocation":"openapi.yaml"},"subdomain":"localhost","siteName":"Citadel API","title":"Publish a signed policy draft - Citadel API","navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"originalFileLocation":"api-reference/policy/publish-a-signed-policy-draft.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"system\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Hands in your policy key\u0026#x27;s signature over a draft\u0026#x27;s bytes. Citadel never signs a policy document: your engine installs only what your policy key (key B) signed. Citadel checks the signature against the policy key your engine registered when it joined. The draft does not go to your engine yet. It becomes pending, and an approval request opens in the console with the bar of your approval rules in force: as many approvals as their strictest rule asks, from that rule\u0026#x27;s group. When no approval rules are in force, or they hold nothing, one member of your organization approves. Approvers confirm with their passkeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (published), which verifies the signature again and installs it. Declined or not approved in time, it is rejected and never sent. A newer change replaces one still waiting, and so does a newer version published in the console: the older one reads rejected, rejected_reason: superseded. A rejected version number is never used again. Sending the same version and signature again answers the change as it stands, so a retry after a lost answer is safe. No network. Needs the policy.write scope. A key limited to vaults is refused with 403 vault_limited.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Citadel API\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"https://mintlify.s3.us-west-1.amazonaws.com/local/_generated/favicon/apple-touch-icon.png?v=3\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"https://mintlify.s3.us-west-1.amazonaws.com/local/_generated/favicon/favicon-32x32.png?v=3\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"https://mintlify.s3.us-west-1.amazonaws.com/local/_generated/favicon/favicon-16x16.png?v=3\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"https://mintlify.s3.us-west-1.amazonaws.com/local/_generated/favicon/favicon.ico?v=3\">\u003cmeta data-mint-head property=\"og:title\" content=\"Publish a signed policy draft - Citadel API\">\u003cmeta data-mint-head property=\"og:description\" content=\"Hands in your policy key\u0026#x27;s signature over a draft\u0026#x27;s bytes. Citadel never signs a policy document: your engine installs only what your policy key (key B) signed. Citadel checks the signature against the policy key your engine registered when it joined. The draft does not go to your engine yet. It becomes pending, and an approval request opens in the console with the bar of your approval rules in force: as many approvals as their strictest rule asks, from that rule\u0026#x27;s group. When no approval rules are in force, or they hold nothing, one member of your organization approves. Approvers confirm with their passkeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (published), which verifies the signature again and installs it. Declined or not approved in time, it is rejected and never sent. A newer change replaces one still waiting, and so does a newer version published in the console: the older one reads rejected, rejected_reason: superseded. A rejected version number is never used again. Sending the same version and signature again answers the change as it stands, so a retry after a lost answer is safe. No network. Needs the policy.write scope. A key limited to vaults is refused with 403 vault_limited.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Citadel API\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://local./api-reference/policy/publish-a-signed-policy-draft\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Publish a signed policy draft - Citadel API\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Hands in your policy key\u0026#x27;s signature over a draft\u0026#x27;s bytes. Citadel never signs a policy document: your engine installs only what your policy key (key B) signed. Citadel checks the signature against the policy key your engine registered when it joined. The draft does not go to your engine yet. It becomes pending, and an approval request opens in the console with the bar of your approval rules in force: as many approvals as their strictest rule asks, from that rule\u0026#x27;s group. When no approval rules are in force, or they hold nothing, one member of your organization approves. Approvers confirm with their passkeys and see every whitelist address added and removed, in full. Once approved, Citadel sends the document to your engine (published), which verifies the signature again and installs it. Declined or not approved in time, it is rejected and never sent. A newer change replaces one still waiting, and so does a newer version published in the console: the older one reads rejected, rejected_reason: superseded. A rejected version number is never used again. Sending the same version and signature again answers the change as it stands, so a retry after a lost answer is safe. No network. Needs the policy.write scope. A key limited to vaults is refused with 403 vault_limited.\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"https://mintlify.s3.us-west-1.amazonaws.com/local/_generated/favicon/browserconfig.xml?v=3\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Citadel API\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#1F5BD8\">\u003clink data-mint-head rel=\"canonical\" href=\"https://local./api-reference/policy/publish-a-signed-policy-draft\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/api-reference/policy/publish-a-signed-policy-draft.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@type\":\"WebSite\",\"name\":\"Citadel API\",\"creator\":{\"@type\":\"Organization\",\"name\":\"Mintlify\",\"url\":\"https://mintlify.com\"}}\u003c/script>","siteName":"Citadel API","nav":{"tabs":[{"tab":"Guides","pages":[{"title":"Guides","href":""}]},{"tab":"API reference","groups":[{"group":"Wallets","pages":[{"title":"List wallets","openapi":"/openapi.yaml get /wallets","href":"/api-reference/wallets/list-wallets"},{"title":"Issue a deposit wallet","openapi":"/openapi.yaml post /wallets","href":"/api-reference/wallets/issue-a-deposit-wallet"},{"title":"Issue wallets in a batch","openapi":"/openapi.yaml post /wallets/batch","href":"/api-reference/wallets/issue-wallets-in-a-batch"},{"title":"Get a wallet","openapi":"/openapi.yaml get /wallets/{reference}","href":"/api-reference/wallets/get-a-wallet"},{"title":"Update a wallet","openapi":"/openapi.yaml patch /wallets/{reference}","href":"/api-reference/wallets/update-a-wallet"},{"title":"Retire a wallet","openapi":"/openapi.yaml post /wallets/{reference}/retire","href":"/api-reference/wallets/retire-a-wallet"},{"title":"Get a wallet balance","openapi":"/openapi.yaml get /wallets/{reference}/balance","href":"/api-reference/wallets/get-a-wallet-balance"},{"title":"Get a wallet's limits","openapi":"/openapi.yaml get /wallets/{reference}/limits","href":"/api-reference/wallets/get-a-wallets-limits"}]},{"group":"Transactions","pages":[{"title":"List transactions","openapi":"/openapi.yaml get /transactions","href":"/api-reference/transactions/list-transactions"},{"title":"Send a transaction","openapi":"/openapi.yaml post /transactions","href":"/api-reference/transactions/send-a-transaction"},{"title":"Estimate a transaction","openapi":"/openapi.yaml post /transactions/estimate","href":"/api-reference/transactions/estimate-a-transaction"},{"title":"Get a transaction","openapi":"/openapi.yaml get /transactions/{id}","href":"/api-reference/transactions/get-a-transaction"},{"title":"Cancel a transaction","openapi":"/openapi.yaml post /transactions/{id}/cancel","href":"/api-reference/transactions/cancel-a-transaction"},{"title":"Speed up a stuck EVM transaction","openapi":"/openapi.yaml post /transactions/{id}/speedup","href":"/api-reference/transactions/speed-up-a-stuck-evm-transaction"},{"title":"Validate an address","openapi":"/openapi.yaml get /addresses/validate","href":"/api-reference/transactions/validate-an-address"}]},{"group":"Transfers","pages":[{"title":"List transfers","openapi":"/openapi.yaml get /transfers","href":"/api-reference/transfers/list-transfers"},{"title":"Get a transfer","openapi":"/openapi.yaml get /transfers/{tx_hash}","href":"/api-reference/transfers/get-a-transfer"}]},{"group":"Reference","pages":[{"title":"List networks and chains","openapi":"/openapi.yaml get /networks","href":"/api-reference/reference/list-networks-and-chains"},{"title":"List assets","openapi":"/openapi.yaml get /assets","href":"/api-reference/reference/list-assets"},{"title":"List asset prices","openapi":"/openapi.yaml get /prices","href":"/api-reference/reference/list-asset-prices"},{"title":"Get network status","openapi":"/openapi.yaml get /status","href":"/api-reference/reference/get-network-status"}]},{"group":"Organization","pages":[{"title":"List vaults","openapi":"/openapi.yaml get /vaults","href":"/api-reference/organization/list-vaults"},{"title":"Get organization balances","openapi":"/openapi.yaml get /balances","href":"/api-reference/organization/get-organization-balances"},{"title":"Get the calling key","openapi":"/openapi.yaml get /me","href":"/api-reference/organization/get-the-calling-key"},{"title":"Get the billing balance","openapi":"/openapi.yaml get /billing/balance","href":"/api-reference/organization/get-the-billing-balance"}]},{"group":"Approvals","pages":[{"title":"List approvals","openapi":"/openapi.yaml get /approvals","href":"/api-reference/approvals/list-approvals"}]},{"group":"Policy","pages":[{"title":"Get the policy document","openapi":"/openapi.yaml get /policy","href":"/api-reference/policy/get-the-policy-document"},{"title":"Draft the policy document","openapi":"/openapi.yaml post /policy/drafts","href":"/api-reference/policy/draft-the-policy-document"},{"title":"Get a policy draft","description":"One version of the policy document, drafted here or in the console, and what became of it:\n\n- `draft` — sign `document` and publish it;\n- `pending` — signed; waits for approval (`approval`);\n- `published` — sent to your engine; `GET /policy` reads what is in force;\n- `rejected` — never sent; `rejected_reason` says why.\n\nRead it to follow a change after `POST /policy/publish`, or to fetch a draft's bytes again\nbefore you sign. No `network`. Needs the `read` scope. A key limited to vaults is refused\nwith `403 vault_limited`.\n","openapi":"/openapi.yaml get /policy/drafts/{version}","href":"/api-reference/policy/get-a-policy-draft"},{"title":"Publish a signed policy draft","openapi":"/openapi.yaml post /policy/publish","href":"/api-reference/policy/publish-a-signed-policy-draft"}]},{"group":"Workflows","pages":[{"title":"Start workflows by event","description":"Sends your own named event (`payout`, `refund`, …) on one network with the inputs your\nworkflow declared. Every active workflow of that network listening on the name starts a\nrun; a workflow on another network never hears it. The platform does the rest —\nscreening, choosing a wallet, moving funds — the way the workflow is drawn.\n\n`network` is required: there is no default network. Inputs are type-checked at the\ndoor on that network: a wallet must be yours there, an asset supported there.\nIdempotent per (event, idempotency key, workflow): a retry with the same inputs returns\nthe runs it started and starts nothing twice. The key reused with other inputs, or on\nanother network, is refused with `409 idempotency_conflict`, and nothing starts.\n\nNeeds the `events.write` scope. A key limited to vaults is refused with\n`403 vault_limited`: workflows choose their own wallets.\n","openapi":"/openapi.yaml post /events/{name}","href":"/api-reference/workflows/start-workflows-by-event"},{"title":"List workflow runs","openapi":"/openapi.yaml get /runs","href":"/api-reference/workflows/list-workflow-runs"},{"title":"Get a workflow run","openapi":"/openapi.yaml get /runs/{id}","href":"/api-reference/workflows/get-a-workflow-run"},{"title":"Cancel a workflow run","openapi":"/openapi.yaml post /runs/{id}/cancel","href":"/api-reference/workflows/cancel-a-workflow-run"},{"title":"List workflows","openapi":"/openapi.yaml get /workflows","href":"/api-reference/workflows/list-workflows"}]},{"group":"Webhook endpoints","pages":[{"title":"List webhook endpoints","openapi":"/openapi.yaml get /webhook_endpoints","href":"/api-reference/webhook-endpoints/list-webhook-endpoints"},{"title":"Add a webhook endpoint","openapi":"/openapi.yaml post /webhook_endpoints","href":"/api-reference/webhook-endpoints/add-a-webhook-endpoint"},{"title":"Get a webhook endpoint","openapi":"/openapi.yaml get /webhook_endpoints/{id}","href":"/api-reference/webhook-endpoints/get-a-webhook-endpoint"},{"title":"Remove a webhook endpoint","openapi":"/openapi.yaml delete /webhook_endpoints/{id}","href":"/api-reference/webhook-endpoints/remove-a-webhook-endpoint"},{"title":"Change a webhook endpoint","openapi":"/openapi.yaml patch /webhook_endpoints/{id}","href":"/api-reference/webhook-endpoints/change-a-webhook-endpoint"},{"title":"Send a test delivery","openapi":"/openapi.yaml post /webhook_endpoints/{id}/test","href":"/api-reference/webhook-endpoints/send-a-test-delivery"},{"title":"Rotate the signing secret","openapi":"/openapi.yaml post /webhook_endpoints/{id}/rotate_secret","href":"/api-reference/webhook-endpoints/rotate-the-signing-secret"}]},{"group":"Webhook deliveries","pages":[{"title":"List webhook deliveries","openapi":"/openapi.yaml get /webhook_deliveries","href":"/api-reference/webhook-deliveries/list-webhook-deliveries"},{"title":"Retry a failed delivery","openapi":"/openapi.yaml post /webhook_deliveries/{id}/retry","href":"/api-reference/webhook-deliveries/retry-a-failed-delivery"},{"title":"Retry the failed deliveries of a period","openapi":"/openapi.yaml post /webhook_deliveries/retry","href":"/api-reference/webhook-deliveries/retry-the-failed-deliveries-of-a-period"}]},{"group":"Reports","pages":[{"title":"Ask for a report","openapi":"/openapi.yaml post /reports","href":"/api-reference/reports/ask-for-a-report"},{"title":"Get a report","openapi":"/openapi.yaml get /reports/{id}","href":"/api-reference/reports/get-a-report"},{"title":"Download a report's file","openapi":"/openapi.yaml get /reports/{id}/file","href":"/api-reference/reports/download-a-reports-file"}]},{"group":"API Reference","pages":[{"title":"Operation*","openapi":"/openapi.yaml webhook operation","href":"/api-reference/operation*"},{"title":"Blockchain event*","openapi":"/openapi.yaml webhook blockchain_event","href":"/api-reference/blockchain_event*"},{"title":"Run*","openapi":"/openapi.yaml webhook run","href":"/api-reference/run*"},{"title":"Wallet*","openapi":"/openapi.yaml webhook wallet","href":"/api-reference/wallet*"}]}]}]},"basePath":"","title":"Publish a signed policy draft - Citadel API","buildId":"3c5c97139bf8","dynamics":["c2aa737661","2b790eda92","b0f84f53ff","a9bb46feb3"],"chunks":["OpenApiReferenceProvider-DGp7Pxzx.js","utils-8Jv-1L07.js","cloneDeep-izs3Hzxb.js","_Uint8Array-BVIqJ90l.js","isObjectLike-C8vmzPEN.js","isArray-Dcu1dQ5t.js","utils-CLSsw4wM.js","toString-BX-iyh8G.js","isSymbol-CSH9U2Gm.js","addUniqueKeysToSchema-RrVIaDh7.js","snippetPresets-DSI4wgEf.js","style-to-object-BY3JEAdk.js","v4-DIgz8zy6.js","conditional-requirements-CFM0id0Q.js","ApiExamples-W2e9g_oX.js","isEqual-BfaiBbcl.js","usePlaygroundInputsStore-CDpwfEDw.js","shallow-Dq5-SwG1.js","getApiPlaygroundDisplay-basMZYRO.js","CodeSnippet-CWKJfCQt.js","CodeSnippets-xDLW1FEv.js","Page-Dc5qkJG8.js","getPastedJson-DObxGgIR.js","icon-HMNWawTa.js","MethodPill-DA_6XsWN.js","DuplicateIcon-DK5o2DOi.js","createIcon-BhUMOhLi.js","DynamicMarkdown-Dokq7GGW.js","SectionHeading-DSaxk-9e.js","axios-vUo_bRtp.js","Markdown-BDO6y5hF.js","clsx-DfU2JnNK.js","lib-DuNMoZ05.js","lib-B8HbPgcC.js","html-void-elements-mCSPHezU.js","dist-BRVLaqXg.js","katex-YMM-FMeD.js","marked.esm-CZWythHk.js","AssistantPageLink-CxOoiG7S.js"]}