--- name: headlessdomains_mpp version: 1.1.0 description: Machine Payments Protocol (MPP) canonical flow for HeadlessDomains. homepage: https://headlessdomains.com metadata: {"headlessdomains_mpp":{"emoji":"💸","category":"payments","api_base":"https://headlessdomains.com"}} --- # HeadlessDomains MPP Integration Skill > **URL:** https://headlessdomains.com/skill_mpp.md > **Platform:** HeadlessDomains (Handshake Domain Management) > **Auth:** `X-API-Key` (For Headless Agents) > **Main Skill File:** [View SKILL.md](https://headlessdomains.com/SKILL.md) ## Overview This skill describes the canonical Machine Payments Protocol (MPP) flow for registering or renewing domains on HeadlessDomains using Tempo-backed payments. The goal is simple: let an agent complete the `402 Payment Required` challenge-response loop without losing request identity, dropping the request body, or misconfiguring wallet signing mode. ## The Core Problem: Auth Header Collision Many MPP client flows retry the original request by replacing the `Authorization` header with: ```http Authorization: Payment ``` If your original request used: ```http Authorization: Bearer hd_agent_... ``` then the retry loses agent identity. That can break session continuity, lock handling, or payment verification. ## The Fix: Use `X-API-Key` for Agent Identity HeadlessDomains supports: ```http X-API-Key: hd_agent_YOUR_KEY ``` This is the preferred auth mechanism for agent-side MPP purchases because the payment retry can replace `Authorization` while preserving `X-API-Key`. ## Recommended Canonical Agent Path For production agent environments, the recommended client path is: - **Primary:** `pympp` / Python - **Secondary:** `mppx`, only if your environment already supports its key storage model Why: - `mppx` can still work in some environments, but agents should not assume its key storage and CLI behavior are portable across headless containers - Python gives agents more explicit control over the 402 replay - Preserving JSON request shape on retry is critical --- ## Runtime & Environment Reality Checks ### Python / pympp - `pympp[tempo]` requires **Python 3.11+** - If your base image is 3.10 or older, install Python 3.11 in a venv or side environment - If `pip install "pympp[tempo]==0.6.0"` does not resolve correctly from your index, use the GitHub fallback: ```bash pip install "pympp[tempo] @ git+https://github.com/tempoxyz/pympp@v0.6.0" ``` ### mppx - `mppx` is not a safe primary path unless you have already verified its real CLI syntax in your environment - In mppx v0.6.3, agents should check: ```bash mppx --help mppx sign --help ``` - In headless containers, `mppx account create` may fail with: ``` spawn secret-tool ENOENT ``` This means the environment lacks keyring support (`secret-tool` / `libsecret`), so `mppx` cannot store or access keys normally. ### Headless container note If using `mppx` in a server/container environment, be prepared to install and run: - `libsecret-tools` - `libsecret-1-0` - `gnome-keyring` - `dbus-x11` Even then, some containers still behave poorly. If so, use `pympp`. --- ## Canonical Flow ### 1. Initial request Send the purchase request with: - `X-API-Key` - `X-Payment-Method: mpp` - JSON body Example: ```http POST https://headlessdomains.com/api/v1/domains/register X-API-Key: hd_agent_YOUR_KEY X-Payment-Method: mpp Content-Type: application/json {"domain":"myname","tld":"chatbot","years":1} ``` ### 2. Challenge HeadlessDomains returns: - HTTP `402 Payment Required` - `WWW-Authenticate` challenge for MPP ### 3. Sign Use a Tempo-capable MPP client (`pympp` recommended) to create a payment credential from the challenge. ### 4. Retry Retry the same POST with: - the same URL - the same JSON body - the same `Content-Type` - the same `X-API-Key` - the same `X-Payment-Method` - plus: - `Authorization: Payment ` ### 5. Success HeadlessDomains verifies the payment receipt and completes the registration. --- ## ⚠️ Critical Replay Requirement: Preserve JSON Exactly When replaying the request after 402, you **must** preserve all of the following: - **Method:** `POST` - **URL:** identical to the original request - **Headers:** - `X-API-Key: hd_agent_YOUR_KEY` - `X-Payment-Method: mpp` - `Content-Type: application/json` - `Authorization: Payment ` ← newly added - **Body:** the exact same JSON payload, for example: ```json {"domain":"myname","tld":"chatbot","years":1} ``` ### Common failure mode: `302 → /search` If your retry drops JSON and falls back to form data, or loses `Content-Type: application/json`, HeadlessDomains may route the request through the HTML form branch instead of the MPP API path. Typical symptom: - HTTP `302` - `Location: /search` This usually means the retry request was malformed, not that payment verification succeeded. **Debugging tip:** disable automatic redirect following while testing so you can see the raw `302 Location: /search` response instead of a misleading HTML page. --- ## Wallet Modes ### A. Fresh self-owned wallet Use this when the wallet is newly provisioned and directly controlled by the agent. Recommended settings: - `TempoAccount.from_key(...)` - **no `root_account`** - direct signing by the wallet's own key This is the simplest and most reliable path for freshly generated, directly funded wallets. ### B. Keychain / authorized-signer mode Use this only when signing on behalf of a Tempo smart account that already has an authorized signer relationship configured on-chain. This mode may involve `root_account` or account-keychain semantics. If the signer key is not registered, you may see: ``` AccountKeychainError(KeyNotFound(KeyNotFound)) ``` If that happens, the key is not authorized for that account and the transaction will fail even if the payment challenge was otherwise well-formed. --- ## Minimal Python Pattern (pympp) ```python from mpp import parse_www_authenticate, format_authorization from mpp.methods.tempo import TempoAccount, tempo, ChargeIntent account = TempoAccount.from_key(PRIVATE_KEY) challenge = parse_www_authenticate(www_authenticate_header) method = tempo( account=account, chain_id=4217, intents={"charge": ChargeIntent(chain_id=4217)}, ) credential = await method.create_credential(challenge) payment_header = format_authorization(credential) ``` Then retry the original request with: - the same JSON body - the same `X-API-Key` - the same `X-Payment-Method` - `Content-Type: application/json` - `Authorization: Payment ` --- ## Troubleshooting ### Stale locks If a payment flow is interrupted, the domain may remain locked temporarily. You can clear the lock: ```bash curl -X POST https://headlessdomains.com/api/v1/domains/myname.chatbot/reset-lock \ -H "X-API-Key: hd_agent_YOUR_KEY" ``` ### Invalid payment authorization If you see: - `Payment authorization did not verify` - `Invalid payment authorization` - `AccountKeychainError(KeyNotFound)` check: 1. Are you using `X-API-Key` instead of `Bearer` auth? 2. Did you preserve JSON + `Content-Type: application/json` on retry? 3. Are you accidentally using keychain mode (`root_account`) for a fresh wallet? 4. Is your signer actually authorized on-chain for the target account? ### Redirected HTML instead of JSON If your client gets a `200` with HTML or a `302` to `/search`, your retry likely dropped the JSON request body or content type. --- ## Related Skills - Main skill: [SKILL.md](https://headlessdomains.com/SKILL.md) - Platform homepage: [https://headlessdomains.com](https://headlessdomains.com/) - Documentation: [https://docs.headlessdomains.com](https://docs.headlessdomains.com/)