{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/x402-payments-overview-04a296ff/python-x402-payout-and-policy","markdown_url":"https://test.abhinandan.one/x402-payments-overview-04a296ff/python-x402-payout-and-policy.md","article":{"id":"a663e9ab-91ce-4b40-8006-bf79a08e0566","article_slug":"python-x402-payout-and-policy","parent_article_slug":"x402-payments-overview-04a296ff","parent_article_title":"x402 Payments Overview","kind":"guide","published_at":"2026-08-11T18:55:04.863049+00:00","keywords":["register_payout_address","X402Client","set_spend_policy","get_spend_policy","PrivateKeySigner","list_payout_addresses"],"meta_description":"register_payout_address signs an ownership message locally, then set_spend_policy configures caps and an allowlist to guard outbound x402 payments.","og_image_url":null,"source_file_paths":["sdk-python/src/primitive/x402/client.py","sdk-python/README.md","sdk-python/tests/test_x402_client.py"],"recording_id":null,"replayable":false,"task_name":"Registering Payout Addresses and Spend Policy","category":"Python SDK","summary":null,"description":"Register a payee payout address with a proof-of-ownership signature so charge() can resolve pay_to, and configure the org's spend policy to cap and allowlist outbound x402 payments.","content_kind":"repo_page","content_markdown":"Every payee registers a payout address exactly once before it can receive x402 payments. Every org can also configure a spend policy that caps and allowlists the payments it sends. Do the first before your first `charge()`; do the second any time you want a kill-switch, per-payment/daily caps, or a payee allowlist on outbound payments.\n\nFor the full non-custodial payment model (four-step flow, amount formats, networks), see [x402 Payments Overview](x402-payments-overview). For creating and settling challenges once a payout address exists, see [Creating and Paying Challenges](python-x402-charge-and-pay).\n\n## Register a payout address\n\n`X402Client.register_payout_address` is the Python entry point for [x402 payout address registration](x402-payments-overview): it proves control of a wallet with a local ownership signature and sets that address as your org's default payout destination for a network, so `charge()` can resolve `pay_to`.\n\n<Steps>\n\n<Step title=\"Create the x402 client and signer\">\n\n```python\nimport os\nimport primitive\n\nx402 = primitive.create_x402_client(api_key=os.environ[\"PRIMITIVE_API_KEY\"])\npayee = primitive.PrivateKeySigner(os.environ[\"PAYEE_KEY\"])\n```\n\n`PrivateKeySigner` holds the wallet key in process memory. The key is never sent to Primitive; it signs the ownership message locally.\n\n</Step>\n\n<Step title=\"Register the address\">\n\n```python\nresult = x402.register_payout_address(\n    signer=payee,\n    network=\"base-sepolia\",\n    label=\"treasury\",\n)\n\nprint(result.is_default, result.address, result.verified_at)\n```\n\n`network` is `base-sepolia` (testnet) or `base` (mainnet). `label` is optional; it comes back on each entry from `list_payout_addresses`.\n\n</Step>\n\n<Step title=\"Verify the address is registered\">\n\n```python\naddresses = x402.list_payout_addresses()\nprint(addresses)\n```\n\nExpect at least one entry with `network=\"base-sepolia\"` and `is_default=True`. This is the address `charge()` will fill in as `pay_to` for challenges you create on that network.\n\n</Step>\n\n</Steps>\n\n<Tip>\n\nRegister once per network you plan to charge on. There's no per-charge payout address argument, `charge()` always resolves `pay_to` from your org's registered default for the challenge's `network`.\n\n</Tip>\n\n<Warning>\n\n`register_payout_address` requires a signer that supports the ownership-proof signature (`sign_message` under the hood). Passing a signer that can only do EIP-712 typed-data signing (the kind `pay()` needs) raises an `X402Error` naming the missing capability.\n\n</Warning>\n\n## Read and update the spend policy\n\nThe [spend policy](x402-payments-overview) guards every outbound x402 payment your org makes: a `paused` kill-switch, `max_per_payment` and `max_per_day` caps expressed in token base units (USDC has 6 decimals, so `\"5000000\"` is 5.00 USDC), and an `allowlist` of payee org ids. Read it with `x402.get_spend_policy()` and update it with `x402.set_spend_policy(...)`.\n\n| Field | Meaning |\n|---|---|\n| `paused` | When `True`, all outbound payments are refused. |\n| `max_per_payment` | Cap in token base units, or `None` for no cap. |\n| `max_per_day` | Daily cap in token base units, or `None` for no cap. |\n| `allowlist` | Allowed payee org ids. `None` means any on-net payee is allowed; `[]` denies all. |\n\n```python\npolicy = x402.get_spend_policy()\nprint(policy.paused, policy.max_per_payment, policy.max_per_day, policy.allowlist)\n```\n\n`set_spend_policy` merges: only the fields you pass change, and omitted fields keep their current value.\n\n```python\nx402.set_spend_policy({\"paused\": False, \"max_per_payment\": \"5000000\"})\n```\n\nPass `None` explicitly for a field to clear that cap:\n\n```python\nx402.set_spend_policy({\"max_per_day\": None})\n```\n\n<Tip>\n\nUse `paused: True` as an emergency stop for outbound payments without touching caps or the allowlist, a single-field update leaves everything else as-is.\n\n</Tip>\n\n## List registered payout addresses\n\n`X402Client.list_payout_addresses` returns every payout address registered for your org, across all networks:\n\n```python\nfor addr in x402.list_payout_addresses():\n    print(addr.network, addr.address, addr.label, addr.is_default)\n```\n\nEach entry carries `id`, `address`, `network`, `label`, `is_default`, and `verified_at`.\n\n## Errors\n\nEvery method on this page raises `primitive.X402Error` on a client-side, transport, or non-2xx server error. `register_payout_address`, `get_spend_policy`, `set_spend_policy`, and `list_payout_addresses` all raise `primitive.X402Error` on a client-side, transport, or non-2xx server error. `status` is the HTTP status, or `0` when the request never reached the server; `body` carries the parsed error envelope when present; `retry_after` carries the `Retry-After` header when the server sent one. See the [Python SDK Error Reference](python-errors-reference) for the full catalog.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Creating and Paying Challenges\" href=\"python-x402-charge-and-pay\">\n\nCreate a payment challenge as the payee and settle it as the payer once your payout address is registered.\n\n</Card>\n\n<Card title=\"Email-Native Payments\" href=\"python-x402-email-payments\">\n\nIssue and pay an x402 challenge that rides a real email thread instead of an out-of-band channel.\n\n</Card>\n\n<Card title=\"Low-Level Payment Signing\" href=\"python-x402-signing-primitives\">\n\nDrive nonce derivation, validity windows, and EIP-712 signing directly when pay() doesn't fit your flow.\n\n</Card>\n\n<Card title=\"x402 Payments Overview\" href=\"x402-payments-overview\">\n\nReview the shared non-custodial payment model across every SDK before customizing your integration.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/sdk-python/src/primitive/x402/client.py","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Registering+Payout+Addresses+and+Spend+Policy&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fpython-x402-payout-and-policy","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}