Skill: markmcdaniels/agent-payment-processor (version 2.9.1)

--- name: paegents-pay description: >- Use Paegents through the published SDK and API surface to register services, create usage agreements, activate bilateral escrow, route metered usage, and settle with the recommended execution mode. license: MIT compatibility: - claude-code - openclaw - cursor metadata: version: "2.9.1" author: paegents homepage: https://paegents.com repository: https://github.com/MarkMcDaniels/paegents-pay-skill payment_rail: stablecoin escrow_model: bilateral networks: base,base-sepolia asset: USDC beta: true openclaw: requires: env: - PAEGENTS_API_URL - PAEGENTS_API_KEY - PAEGENTS_AGENT_ID primaryEnv: PAEGENTS_API_KEY allowed-tools: Read Write Edit WebFetch --- # Paegents Pay Use this skill when an agent needs to buy or sell a service through Paegents without bypassing the public product surface. Keep the guidance at the integration layer. Do not expose private keys, seller-side secrets, unpublished endpoints, internal architecture, or internal operations detail. Explain what the system expects and why the workflow is structured that way. ## Why The Product Works This Way - **Client-side signing** keeps payment authority with the wallet owner instead of the platform. - **Agreement creation and activation are separate** because commercial acceptance is not the same thing as on-chain funding. - **The activation package is the source of truth** because fees, spenders, nonces, and chain parameters must come from live state. - **Settlement mode is chosen at runtime** because direct wallet execution and sponsored execution depend on current signer and agreement state. - **Usage and settlement stay tied to agreement state** so both sides can monitor whether the agreement is merely accepted, actually funded, or ready for settlement. ## Use This When - a buyer agent needs to purchase a metered or fixed-quantity service - a seller agent needs to list a service and accept agreement-backed payments - an operator needs to understand the next valid public action for an agreement - bilateral escrow, metered usage, or settlement mode selection is part of the task ## Do Not Use This When - the task is asking for internal implementation details rather than product usage - someone wants to bypass policy, approval, verification, or signing requirements - someone wants to share or log private keys, raw wallet secrets, or seller credentials - a one-off direct payment is being confused with a bilateral escrow agreement ## Preflight Before taking any action, confirm: - the user has the public credentials needed for the chosen flow - agent auth for agent payment operations - owner auth only for owner-scoped setup or policy operations - the buyer wallet is self-custody and controlled by the signer - the wallet has enough USDC for the intended payment or escrow - the signer has gas if the chosen settlement mode requires direct broadcast - the SDK is installed when SDK use is expected Do not ask users to paste secrets into chat. Assume secrets stay in their environment. ## Operating Rules - Prefer SDK calls over hand-built HTTP when both are available. - Prefer live responses over hardcoded assumptions. - Never invent fee amounts, spenders, chain IDs, nonces, or agreement state. - Treat `get_activation_package()` / `getActivationPackage()` as authoritative for bilateral activation. - If the activation package shows a pending infra fee, treat the infra fee permit as a required second buyer authorization. - Call settlement options before choosing direct or sponsored settlement. - Use the current agreement and escrow status before deciding the next step. ## Workflow Map ### Seller 1. Register the service. 2. Configure acceptance behavior and pricing policy. 3. Accept or reject agreements. 4. Deliver through the metered proxy or record usage through the public API. 5. Monitor escrow and settlement state until funds are claimable. ### Buyer: Bilateral Escrow 1. Search or select the service. 2. Create the usage agreement with the commercial terms. 3. Wait for seller acceptance. 4. Fetch the activation package. 5. Sign the buyer activation intent locally. 6. If the package shows a pending infra fee, sign the separate infra fee permit locally. 7. Submit activation and poll until the agreement is active and confirmed. 8. Use the service through the metered path or agreed delivery path. 9. Check settlement options before any close, claim, or withdraw action. ### Buyer: Direct Stablecoin Purchase Use the direct stablecoin helper when the task is a one-off purchase without bilateral escrow. That flow exists because it solves a simpler trust problem: immediate payment for a single purchase, not an ongoing metered agreement. ## State Guide - `proposed` or `pending`: terms exist but the commercial flow is not yet live - `accepted` with activation still pending: both sides agreed, but funds are not yet locked - `active` with confirmed activation: escrow is funded and usage can proceed - activation failed or requires refreshed signatures: inspect the reason, refresh the missing authorization, and retry through the public path ## Which Reference To Read Next - Read [references/QUICK_START.md](references/QUICK_START.md) for the shortest end-to-end public path. - Read [references/PAYMENT_FLOWS.md](references/PAYMENT_FLOWS.md) when you need lifecycle guidance and decision rules. - Read [references/SDK_USAGE.md](references/SDK_USAGE.md) for Python and TypeScript method mapping. - Read [references/API_REFERENCE.md](references/API_REFERENCE.md) only when the SDK is not enough. - Read [references/ERROR_CODES.md](references/ERROR_CODES.md) when the task is blocked on an API or state error. - Read [references/RATE_LIMITS.md](references/RATE_LIMITS.md) when the task involves retries, polling, or bulk operations. ## Public Support Boundary Stay within what a customer or operator can do through the product: - published SDK methods - published API routes - documented activation, metering, and settlement flows - documented error handling and retry behavior Do not answer with internal-only remediation steps when a public next action exists.

path: assets/openapi-subset.json
content: {
  "openapi": "3.0.3",
  "info": {
    "title": "Paegents Pay — AgentSkills Subset",
    "version": "2.9.1",
    "description": "Public endpoints relevant to the paegents-pay skill."
  },
  "servers": [
    {"url": "http://localhost:8000", "description": "Local development"},
    {"url": "https://app.paegents.com/api", "description": "Production"}
  ],
  "paths": {
    "/health": {
      "get": {
        "summary": "Health check",
        "operationId": "healthCheck",
        "responses": {
          "200": {
            "description": "OK",
            "content": {"application/json": {"schema": {"type": "object", "properties": {"status": {"type": "string"}}}}}
          }
        }
      }
    },
    "/payment-requirements": {
      "get": {
        "summary": "Get payment parameters",
        "operationId": "getPaymentRequirements",
        "parameters": [
          {"name": "amount_cents", "in": "query", "required": true, "schema": {"type": "integer", "minimum": 1}},
          {"name": "network", "in": "query", "schema": {"type": "string", "default": "base-sepolia"}}
        ],
        "responses": {
          "200": {
            "description": "Payment parameters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "pay_to": {"type": "string"},
                    "asset": {"type": "string"},
                    "network": {"type": "string"},
                    "scheme": {"type": "string"},
                    "amount_cents": {"type": "integer"},
                    "total_required_cents": {"type": "integer"}
                  }
                }
              }
            }
          }
        }
      }
    },
    "/catalog/search": {
      "post": {
        "summary": "Search service catalog",
        "operationId": "catalogSearch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {"type": "string"},
                  "category": {"type": "string"},
                  "min_price": {"type": "integer"},
                  "max_price": {"type": "integer"},
                  "limit": {"type": "integer", "default": 50},
                  "offset": {"type": "integer", "default": 0}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Search results"}
        }
      }
    },
    "/agents/services": {
      "post": {
        "summary": "Register a service",
        "operationId": "registerService",
        "security": [{"apiKey": []}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/RegisterServiceRequest"}
            }
          }
        },
        "responses": {
          "200": {"description": "Service registered", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ServiceResponse"}}}}
        }
      },
      "get": {
        "summary": "List my services",
        "operationId": "listMyServices",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 100}},
          {"name": "offset", "in": "query", "schema": {"type": "integer", "default": 0}}
        ],
        "responses": {
          "200": {"description": "List of services"}
        }
      }
    },
    "/agents/services/{service_id}": {
      "get": {
        "summary": "Get service details",
        "operationId": "getService",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "service_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Service details"}}
      },
      "put": {
        "summary": "Update service",
        "operationId": "updateService",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "service_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}},
        "responses": {"200": {"description": "Service updated"}}
      },
      "delete": {
        "summary": "Deactivate service",
        "operationId": "deleteService",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "service_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Service deactivated"}}
      }
    },
    "/usage-agreements/": {
      "post": {
        "summary": "Create usage agreement",
        "operationId": "createAgreement",
        "security": [{"apiKey": []}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/CreateAgreementRequest"}
            }
          }
        },
        "responses": {
          "200": {"description": "Agreement created", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgreementResponse"}}}}
        }
      }
    },
    "/usage-agreements/{agreement_id}": {
      "get": {
        "summary": "Get agreement details",
        "operationId": "getAgreement",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Agreement details", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/AgreementResponse"}}}}}
      }
    },
    "/usage-agreements/{agreement_id}/accept": {
      "put": {
        "summary": "Accept agreement (seller)",
        "operationId": "acceptAgreement",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Agreement accepted"},
          "402": {"description": "Payment failed"},
          "409": {"description": "Invalid state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/reject": {
      "put": {
        "summary": "Reject agreement (seller)",
        "operationId": "rejectAgreement",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {"type": "string"},
                  "suggested_total_cents": {"type": "integer"}
                }
              }
            }
          }
        },
        "responses": {"200": {"description": "Agreement rejected"}}
      }
    },
    "/usage-agreements/{agreement_id}/record-usage": {
      "post": {
        "summary": "Record usage (seller)",
        "operationId": "recordUsage",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["units_used"],
                "properties": {
                  "units_used": {"type": "integer"},
                  "completed": {"type": "boolean", "default": false}
                }
              }
            }
          }
        },
        "responses": {"200": {"description": "Usage recorded"}}
      }
    },
    "/usage-agreements/{agreement_id}/delivery": {
      "patch": {
        "summary": "Update delivery status (seller only)",
        "operationId": "updateDelivery",
        "description": "Update delivery tracking info for physical goods agreements. Only the seller agent can call this. Agreement must be active or completed.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/UpdateDeliveryRequest"}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delivery updated",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/DeliveryResponse"}
              }
            }
          },
          "403": {"description": "Not the seller agent"},
          "409": {"description": "Invalid delivery transition or agreement state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/cancel": {
      "post": {
        "summary": "Cancel agreement (buyer)",
        "operationId": "cancelAgreement",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {"200": {"description": "Agreement cancelled"}}
      }
    },
    "/usage-agreements/{agreement_id}/escrow-status": {
      "get": {
        "summary": "Get escrow status for agreement",
        "operationId": "getEscrowStatus",
        "description": "Returns the current on-chain escrow state including activation status, settlement status, and claimable amounts.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Escrow status"},
          "404": {"description": "Agreement not found"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/activation-package": {
      "get": {
        "summary": "Get activation package",
        "operationId": "getActivationPackage",
        "description": "Returns the live activation parameters needed for buyer-side signing: terms, nonces, funding options, and pending infra fee details.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Activation package with signing parameters"},
          "409": {"description": "Agreement is not in a state that allows activation"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/activate-escrow": {
      "post": {
        "summary": "Submit signed activation to fund escrow",
        "operationId": "activateEscrow",
        "description": "Submits the buyer's signed activation intent and funding authorization. Processing is asynchronous — a 202 response means the request was accepted, not that the escrow is funded.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["buyer_activation_signature", "funding_authorization"],
                "properties": {
                  "buyer_activation_signature": {"type": "string", "description": "EIP-712 signed activation intent"},
                  "funding_authorization": {"type": "object", "description": "Prior allowance or ERC-2612 permit funding authorization"},
                  "infra_fee_permit_signature": {"type": "string", "description": "EIP-2612 USDC permit for infra fee (required when activation package shows pending fee)"},
                  "infra_fee_permit_deadline": {"type": "integer", "description": "Permit deadline timestamp"}
                }
              }
            }
          }
        },
        "responses": {
          "202": {"description": "Activation accepted for processing"},
          "402": {"description": "Funding authorization invalid or insufficient"},
          "409": {"description": "Agreement is not in a state that allows activation"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/settlement-action-options": {
      "get": {
        "summary": "Get available settlement actions",
        "operationId": "getSettlementActionOptions",
        "description": "Returns which settlement execution modes (direct wallet, sponsored) are currently available for the specified action on this agreement.",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "action", "in": "query", "required": true, "schema": {"type": "string", "enum": ["initiate_close", "seller_claim", "buyer_withdraw"]}}
        ],
        "responses": {
          "200": {"description": "Available settlement options for the requested action"},
          "409": {"description": "Agreement is not in a settlement-eligible state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/claim-instructions": {
      "get": {
        "summary": "Get unsigned calldata for claim or withdraw",
        "operationId": "getClaimInstructions",
        "description": "Returns unsigned transaction calldata for the caller to broadcast from their own wallet. The response includes the contract address, calldata, and expected signer.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Unsigned calldata for claim or withdraw"},
          "409": {"description": "Agreement is not in a claimable state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/initiate-close-calldata": {
      "get": {
        "summary": "Get unsigned calldata for initiating close",
        "operationId": "getInitiateCloseCalldata",
        "description": "Returns unsigned transaction calldata for initiating the settlement close from the caller's own wallet.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Unsigned calldata for initiate close"},
          "409": {"description": "Agreement is not in a closeable state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/sponsored-actions/initiate-close": {
      "post": {
        "summary": "Submit sponsored initiate-close",
        "operationId": "submitSponsoredInitiateClose",
        "description": "Submits a signed initiate-close intent for sponsored (gasless) execution. The platform broadcasts the transaction on behalf of the signer.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["signature", "signer", "nonce", "deadline"],
                "properties": {
                  "signature": {"type": "string"},
                  "signer": {"type": "string"},
                  "nonce": {"type": "integer"},
                  "deadline": {"type": "integer"}
                }
              }
            }
          }
        },
        "responses": {
          "202": {"description": "Sponsored action accepted for processing"},
          "409": {"description": "Agreement is not in a closeable state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/sponsored-actions/seller-claim": {
      "post": {
        "summary": "Submit sponsored seller claim",
        "operationId": "submitSponsoredSellerClaim",
        "description": "Submits a signed seller-claim intent for sponsored (gasless) execution.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["signature", "signer", "nonce", "deadline"],
                "properties": {
                  "signature": {"type": "string"},
                  "signer": {"type": "string"},
                  "nonce": {"type": "integer"},
                  "deadline": {"type": "integer"}
                }
              }
            }
          }
        },
        "responses": {
          "202": {"description": "Sponsored action accepted for processing"},
          "409": {"description": "Agreement is not in a claimable state"}
        }
      }
    },
    "/usage-agreements/{agreement_id}/sponsored-actions/buyer-withdraw": {
      "post": {
        "summary": "Submit sponsored buyer withdraw",
        "operationId": "submitSponsoredBuyerWithdraw",
        "description": "Submits a signed buyer-withdraw intent for sponsored (gasless) execution.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["signature", "signer", "nonce", "deadline"],
                "properties": {
                  "signature": {"type": "string"},
                  "signer": {"type": "string"},
                  "nonce": {"type": "integer"},
                  "deadline": {"type": "integer"}
                }
              }
            }
          }
        },
        "responses": {
          "202": {"description": "Sponsored action accepted for processing"},
          "409": {"description": "Agreement is not in a withdrawable state"}
        }
      }
    },
    "/agents/{agent_id}/events": {
      "get": {
        "summary": "List agent events",
        "operationId": "listAgentEvents",
        "description": "Returns events for the authenticated agent. Supports filtering by event type and cursor-based pagination.",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agent_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "types", "in": "query", "schema": {"type": "string", "description": "Comma-separated event types to filter"}},
          {"name": "after", "in": "query", "schema": {"type": "string", "description": "Cursor for pagination (event ID)"}},
          {"name": "limit", "in": "query", "schema": {"type": "integer", "default": 50}}
        ],
        "responses": {
          "200": {"description": "List of events"}
        }
      }
    },
    "/agents/{agent_id}/events/{event_id}/ack": {
      "post": {
        "summary": "Acknowledge an event",
        "operationId": "ackAgentEvent",
        "description": "Marks an event as acknowledged so it is excluded from future unacknowledged queries.",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agent_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "event_id", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "Event acknowledged"},
          "404": {"description": "Event not found"}
        }
      }
    },
    "/agents/{agent_id}/webhooks": {
      "post": {
        "summary": "Create webhook subscription",
        "operationId": "createWebhook",
        "description": "Subscribe to event notifications delivered to the specified URL. Events are signed with HMAC for verification.",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agent_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["url", "event_types"],
                "properties": {
                  "url": {"type": "string", "format": "uri", "description": "HTTPS endpoint to receive webhook deliveries"},
                  "event_types": {"type": "array", "items": {"type": "string"}, "description": "Event types to subscribe to"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Webhook subscription created"}
        }
      },
      "get": {
        "summary": "List webhook subscriptions",
        "operationId": "listWebhooks",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agent_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "List of webhook subscriptions"}
        }
      }
    },
    "/agents/{agent_id}/webhooks/{subscription_id}": {
      "delete": {
        "summary": "Delete webhook subscription",
        "operationId": "deleteWebhook",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agent_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "subscription_id", "in": "path", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {
          "200": {"description": "Webhook subscription deleted"},
          "404": {"description": "Subscription not found"}
        }
      }
    },
    "/invites": {
      "post": {
        "summary": "Create an invite",
        "operationId": "createInvite",
        "description": "Generate an invite token that can be shared to onboard a new agent or user.",
        "security": [{"apiKey": []}],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {"type": "string", "description": "Role to assign on acceptance"},
                  "expires_in_seconds": {"type": "integer", "default": 604800, "description": "Token TTL in seconds (default 7 days)"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"description": "Invite created with token"}
        }
      },
      "get": {
        "summary": "List invites",
        "operationId": "listInvites",
        "security": [{"apiKey": []}],
        "responses": {
          "200": {"description": "List of invites"}
        }
      }
    },
    "/invites/{token}": {
      "get": {
        "summary": "Get invite details",
        "operationId": "getInvite",
        "parameters": [{"name": "token", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Invite details"},
          "404": {"description": "Invite not found or expired"}
        }
      }
    },
    "/invites/{token}/accept": {
      "post": {
        "summary": "Accept an invite",
        "operationId": "acceptInvite",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "token", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {"description": "Invite accepted"},
          "404": {"description": "Invite not found or expired"},
          "409": {"description": "Invite already used"}
        }
      }
    },
    "/metered-proxy/{agreement_id}/{path}": {
      "get": {
        "summary": "Proxy GET to seller API",
        "operationId": "proxyGet",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "path", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "X-Request-Nonce", "in": "header", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {"200": {"description": "Seller response (transparent proxy)"}}
      },
      "post": {
        "summary": "Proxy POST to seller API",
        "operationId": "proxyPost",
        "security": [{"apiKey": []}],
        "parameters": [
          {"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "path", "in": "path", "required": true, "schema": {"type": "string"}},
          {"name": "X-Request-Nonce", "in": "header", "required": true, "schema": {"type": "string"}}
        ],
        "responses": {"200": {"description": "Seller response (transparent proxy)"}}
      }
    },
    "/metered-proxy/{agreement_id}/status": {
      "get": {
        "summary": "Get metered usage status",
        "operationId": "getMeteredStatus",
        "security": [{"apiKey": []}],
        "parameters": [{"name": "agreement_id", "in": "path", "required": true, "schema": {"type": "string"}}],
        "responses": {
          "200": {
            "description": "Usage status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "agreement_id": {"type": "string"},
                    "units_used": {"type": "integer"},
                    "units_total": {"type": "integer"},
                    "units_remaining": {"type": "integer"},
                    "status": {"type": "string"},
                    "escrow_status": {"type": "string"},
                    "unit": {"type": "string"},
                    "price_per_unit_cents": {"type": "integer"}
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key"
      }
    },
    "schemas": {
      "RegisterServiceRequest": {
        "type": "object",
        "required": ["service_name", "category", "price_model", "base_price_cents", "unit"],
        "properties": {
          "service_name": {"type": "string"},
          "description": {"type": "string"},
          "category": {"type": "string"},
          "price_model": {"type": "string", "enum": ["per_unit", "subscription", "tiered"]},
          "base_price_cents": {"type": "integer"},
          "unit": {"type": "string"},
          "min_quantity": {"type": "integer", "default": 1},
          "max_quantity": {"type": "integer"},
          "capabilities": {"type": "object"},
          "metadata": {"type": "object"},
          "api_base_url": {"type": "string", "format": "uri"},
          "api_key": {"type": "string"},
          "api_auth_header": {"type": "string", "default": "X-API-Key"},
          "auto_accept_enabled": {"type": "boolean", "default": true},
          "auto_accept_max_amount_cents": {"type": "integer"},
          "allowed_payment_rails": {"type": "array", "items": {"type": "string"}}
        }
      },
      "ServiceResponse": {
        "type": "object",
        "properties": {
          "service_id": {"type": "string"},
          "agent_id": {"type": "string"},
          "service_name": {"type": "string"},
          "description": {"type": "string"},
          "category": {"type": "string"},
          "price_model": {"type": "string"},
          "base_price_cents": {"type": "integer"},
          "unit": {"type": "string"},
          "is_active": {"type": "boolean"},
          "api_base_url": {"type": "string"},
          "api_key_masked": {"type": "string"},
          "auto_accept_enabled": {"type": "boolean"},
          "auto_accept_max_amount_cents": {"type": "integer"},
          "allowed_payment_rails": {"type": "array", "items": {"type": "string"}}
        }
      },
      "ShippingAddress": {
        "type": "object",
        "required": ["recipient_name", "street1", "city", "state", "postal_code", "country"],
        "properties": {
          "recipient_name": {"type": "string", "minLength": 1, "maxLength": 100},
          "street1": {"type": "string", "minLength": 1, "maxLength": 200},
          "street2": {"type": "string", "maxLength": 200},
          "city": {"type": "string", "minLength": 1, "maxLength": 100},
          "state": {"type": "string", "minLength": 1, "maxLength": 100},
          "postal_code": {"type": "string", "minLength": 1, "maxLength": 20},
          "country": {"type": "string", "minLength": 2, "maxLength": 2, "description": "ISO 3166-1 alpha-2 country code"},
          "phone": {"type": "string", "description": "E.164 format recommended"}
        }
      },
      "CartItem": {
        "type": "object",
        "required": ["description", "quantity", "amount_cents"],
        "properties": {
          "sku": {"type": "string"},
          "description": {"type": "string"},
          "quantity": {"type": "integer", "minimum": 1},
          "unit_price_cents": {"type": "integer"},
          "amount_cents": {"type": "integer", "minimum": 0},
          "attributes": {"type": "object", "description": "Custom attributes (e.g. requires_shipping, color, size)"}
        }
      },
      "CreateAgreementRequest": {
        "type": "object",
        "required": ["seller_agent_id", "quantity", "unit", "price_per_unit_cents"],
        "properties": {
          "seller_agent_id": {"type": "string"},
          "service_id": {"type": "string"},
          "quantity": {"type": "integer", "minimum": 1},
          "unit": {"type": "string"},
          "price_per_unit_cents": {"type": "integer", "minimum": 1},
          "payment_method": {
            "type": "object",
            "properties": {
              "rail": {"type": "string"},
              "provider": {"type": "string"},
              "wallet_address": {"type": "string"},
              "payment_header": {"type": "string"}
            },
            "description": "Payment method details. Optional for bilateral escrow agreements."
          },
          "buyer_wallet_address": {"type": "string", "description": "Buyer's wallet address for on-chain escrow or direct stablecoin payments"},
          "settlement_model": {"type": "string", "enum": ["bilateral_escrow", "direct_payment"], "description": "Settlement model. Defaults to bilateral_escrow when buyer_wallet_address is provided."},
          "activation_version": {"type": "integer", "description": "Activation protocol version (currently 2)"},
          "service_description": {"type": "string"},
          "expires_in_seconds": {"type": "integer", "default": 86400},
          "shipping_address": {"$ref": "#/components/schemas/ShippingAddress"},
          "cart_items": {
            "type": "array",
            "items": {"$ref": "#/components/schemas/CartItem"}
          }
        }
      },
      "UpdateDeliveryRequest": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": {
            "type": "string",
            "enum": ["pending", "shipped", "in_transit", "out_for_delivery", "delivered", "exception"],
            "description": "Delivery status. Must follow valid state transitions."
          },
          "carrier": {
            "type": "string",
            "enum": ["ups", "fedex", "usps", "dhl", "other"],
            "description": "Shipping carrier. Used for auto-generating tracking URLs."
          },
          "tracking_number": {"type": "string", "maxLength": 100, "description": "Carrier tracking number"},
          "tracking_url": {"type": "string", "format": "uri", "description": "Custom tracking URL (overrides auto-generated)"},
          "estimated_delivery": {"type": "string", "format": "date-time", "description": "Estimated delivery date/time (ISO 8601)"},
          "notes": {"type": "string", "maxLength": 500, "description": "Delivery notes"}
        }
      },
      "DeliveryResponse": {
        "type": "object",
        "properties": {
          "agreement_id": {"type": "string"},
          "status": {"type": "string", "description": "Agreement status"},
          "delivery": {
            "type": "object",
            "properties": {
              "status": {"type": "string"},
              "carrier": {"type": "string"},
              "tracking_number": {"type": "string"},
              "tracking_url": {"type": "string"},
              "estimated_delivery": {"type": "string"},
              "notes": {"type": "string"},
              "shipped_at": {"type": "string", "format": "date-time"},
              "delivered_at": {"type": "string", "format": "date-time"},
              "history": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "status": {"type": "string"},
                    "timestamp": {"type": "string", "format": "date-time"}
                  }
                }
              }
            }
          }
        }
      },
      "AgreementResponse": {
        "type": "object",
        "properties": {
          "agreement_id": {"type": "string"},
          "buyer_agent_id": {"type": "string"},
          "seller_agent_id": {"type": "string"},
          "status": {"type": "string", "enum": ["proposed", "accepted", "active", "completed", "cancelled", "disputed", "expired", "rejected"]},
          "settlement_model": {"type": "string", "enum": ["bilateral_escrow", "direct_payment"]},
          "quantity": {"type": "integer"},
          "unit": {"type": "string"},
          "price_per_unit_cents": {"type": "integer"},
          "total_cents": {"type": "integer"},
          "buyer_wallet_address": {"type": "string"},
          "seller_wallet_address": {"type": "string"},
          "activation_status": {"type": "string", "description": "Escrow activation state (ready, submitting, confirmed, failed)"},
          "escrow_status": {"type": "string", "description": "Settlement state (pending, receipt_submitted, challenge_window, finalized, settled)"},
          "gas_wallet_address": {"type": "string", "description": "Platform gas wallet for permit spender authorization"},
          "expires_at": {"type": "string", "format": "date-time"},
          "service_description": {"type": "string"},
          "metadata": {"type": "object"}
        }
      }
    }
  }
}


path: references/API_REFERENCE.md
content: # API Reference

Use this reference when an agent or operator needs the current supported endpoint surface for integration, debugging, or workflow checks.

Base URL: `PAEGENTS_API_URL`

Authenticated agent endpoints use `X-[REDACTED_SECRET]` unless noted otherwise.

This reference is intentionally limited to the public integration surface. Prefer the SDK for complete request and response typing.

## Common

### GET /health
Health check. No auth required.

### GET /receipts/verify/{receipt_id}
Verify a receipt by ID.

## Service Catalog

### POST /agents/services
Register a new service.

Typical inputs are service metadata, pricing, and optional service endpoint configuration. Use the SDK or `assets/openapi-subset.json` for the current request schema.

### PUT /agents/services/{service_id}
Update service config.

### DELETE /agents/services/{service_id}
Deactivate a service.

### GET /agents/services/{service_id}
Get service details.

### GET /agents/services
List your services.

### POST /catalog/search
Search the service catalog. No auth required.

## Usage Agreements

### POST /usage-agreements/
Create a bilateral escrow agreement proposal. Buyer action.

Use the SDK or `assets/openapi-subset.json` for the current request schema. Core inputs are seller, quantity, pricing, and the buyer's locally signed agreement authorization fields.

### GET /usage-agreements/{agreement_id}
Get agreement details.

### GET /usage-agreements/agents/{agent_id}/agreements
List agreements for an agent.

### PUT /usage-agreements/{agreement_id}/accept
Accept a proposed agreement. Seller action.

### PUT /usage-agreements/{agreement_id}/reject
Reject a proposed agreement. Seller action.

### POST /usage-agreements/{agreement_id}/record-usage
Record usage. Seller action.

### POST /usage-agreements/{agreement_id}/cancel
Cancel agreement. Buyer action.

### POST /usage-agreements/{agreement_id}/dispute
Dispute agreement. Buyer action.

### PATCH /usage-agreements/{agreement_id}/delivery
Update delivery status and tracking info. Seller action.

## Escrow and Settlement

### GET /usage-agreements/{agreement_id}/escrow-status
Get current escrow and settlement status.

Use this to inspect settlement state, claimable balances, on-chain linkage, and related agreement wallet information.

### GET /usage-agreements/{agreement_id}/activation-package
Get the V2 activation payload (terms, nonces, funding options) that the buyer must sign locally before activating escrow.

### POST /usage-agreements/{agreement_id}/activate-escrow
Submit a signed V2 activation request (buyer intent signature + funding authorization) to activate the on-chain escrow.

**Activation is required.** Without calling this endpoint after acceptance, the agreement stays at `accepted` / `activation_status=ready` and the metered proxy will reject requests.

**This endpoint returns HTTP 202 (Accepted).** Activation is asynchronous — a successful response does not mean the agreement is already on-chain. Poll `GET /usage-agreements/{agreement_id}` and `GET /usage-agreements/{agreement_id}/escrow-status` until `status=active` and `activation_status=confirmed`. On Base, delays of seconds to minutes are normal due to relayer/worker processing and chain confirmation. If activation remains in `submitting` for more than 5 minutes without an `activation_tx_hash`, treat that as a backend processing issue. If `activation_status` is still `ready`, the call was never submitted — re-fetch the activation package and retry.

### GET /usage-agreements/{agreement_id}/settlement-action-options
Get the current direct-vs-sponsored execution options for a settlement action.

Required query parameter:

- `action`: one of `initiate_close`, `seller_claim`, `buyer_withdraw`

This is the preferred first call before any settlement action.

### GET /usage-agreements/{agreement_id}/claim-instructions
Get unsigned calldata for seller claim or buyer withdraw.

Use this for the direct wallet settlement path.

### GET /usage-agreements/{agreement_id}/initiate-close-calldata
Get unsigned calldata for buyer- or seller-initiated close.

Use this for the direct wallet close path.

### POST /usage-agreements/{agreement_id}/sponsored-actions/initiate-close
Submit a signed sponsored initiate-close request.

### POST /usage-agreements/{agreement_id}/sponsored-actions/seller-claim
Submit a signed sponsored seller-claim request.

### POST /usage-agreements/{agreement_id}/sponsored-actions/buyer-withdraw
Submit a signed sponsored buyer-withdraw request.

Sponsored settlement is optional. Use the settlement action options endpoint first instead of assuming sponsorship is available.

## Metered Proxy

### ANY /metered-proxy/{agreement_id}/{path}
Proxy a buyer request to the seller API through the active agreement.

Use the SDK metered client so the required request metadata is added consistently.

### GET /metered-proxy/{agreement_id}/status
Get usage status for an agreement.

## Events and Webhooks

### GET /agents/{agent_id}/events
List unacknowledged events for an agent.

### POST /agents/{agent_id}/events/{event_id}/ack
Acknowledge an event.

### POST /agents/{agent_id}/webhooks
Create a webhook subscription.

### GET /agents/{agent_id}/webhooks
List webhook subscriptions.

### DELETE /agents/{agent_id}/webhooks/{subscription_id}
Delete a webhook subscription.

Advanced webhook management may exist in the SDK or dashboard, but it is intentionally omitted from this public reference.

## Invites

If the agent you want to buy from is not yet on the platform, send them an invite.

### POST /invites
Send a platform invite. Auth: JWT or API key.

Body: `{ "email": "[REDACTED_SECRET]", "comment": "Optional message" }`

Rate limits: 3 invites per sender per recipient. 30-minute cooldown between re-sends to the same address. Tokens expire after 14 days.

### GET /invites
List invites you have sent. Auth: JWT or API key.

### GET /invites/{token}
Validate an invite token. Public — no auth required. Returns the invite details if the token is valid and not expired.

### POST /invites/{token}/accept
Accept an invite and link it to your account. Auth: JWT.


path: references/ERROR_CODES.md
content: # Error Codes

Use this reference when an agent needs structured recovery logic or when an operator is triaging a failed request.

Errors follow a consistent pattern: an HTTP status plus a JSON body containing `error`, `code`, `message`, and optional `details`.

## Payment Errors (HTTP 402)

Returned when payment authorization or funding fails during agreement activation.

| Code | Meaning | Resolution |
|------|---------|------------|
| `insufficient_balance` | Wallet balance is too low | Fund the wallet, then retry |
| `invalid_signature` | Payment signature is invalid | Rebuild the payment method with the correct signing key |
| `wrong_amount` | Signed amount does not match the required amount | Use the latest `total_required_cents` from `GET /payment-requirements` |
| `nonce_reused` | Payment authorization cannot be reused | Generate a new payment method with the SDK |
| `wrong_network` | Payment was prepared for a different network | Rebuild the payment method for the requested network |
| `facilitator_unavailable` | Payment processing is temporarily unavailable | Retry after a short delay |
| `stablecoin_error` | Generic payment error | Inspect `details` if provided and retry or prompt for operator review |

## Agreement State Errors (HTTP 409)

| Detail | Meaning | Resolution |
|--------|---------|------------|
| `invalid_state:expired` | Agreement expired before acceptance | Create a new agreement |
| `invalid_state:not_proposed` | Agreement is not in a proposal state | Check current agreement status |
| `invalid_state:not_active` | Agreement is not active for this action | Wait until the agreement is active |
| `agreement_not_active:{status}` | Agreement is not active for metered usage | Confirm the agreement is accepted and funded |

## Auth Errors (HTTP 401/403)

| Detail | Meaning | Resolution |
|--------|---------|------------|
| `invalid_api_key` | API key is missing, invalid, or revoked | Check `PAEGENTS_API_KEY` |
| `agent_not_verified` | Agent is not verified for this action | Complete verification in the dashboard |
| `not_owner_of_seller_agent` | Action is restricted to the seller | Use the seller agent's API key |
| `not_buyer_of_agreement` | Action is restricted to the buyer | Use the buyer agent's API key |
| `agent_not_owned_by_user` | Current account does not own the agent | Confirm ownership and use the correct account |

## Validation Errors (HTTP 400)

| Detail | Meaning | Resolution |
|--------|---------|------------|
| `missing_nonce_header` | Required request metadata is missing | Use the SDK metered client or include the required header |
| `service_seller_mismatch` | The service does not belong to the specified seller | Verify `seller_agent_id` and `service_id` |
| `api_base_url required when api_key is provided` | A protected service needs a reachable endpoint URL | Add `api_base_url` |
| `internal_ip_blocked` | The configured endpoint URL is not allowed | Use a public HTTPS URL |
| `payment_header_required` | A payment authorization is required | Use the SDK payment helper before creating the agreement |
| `direct_stablecoin_requires_buyer_wallet_address` | Direct stablecoin agreement created without buyer wallet | Include `buyer_wallet_address` in the agreement creation request |
| `direct_stablecoin_fee_failed:*` | Platform fee collection failed during acceptance | The fee permit may have expired or the wallet may have insufficient USDC. Re-sign the fee permit and retry |

## Delivery Errors (HTTP 403/409)

| Status | Detail | Meaning | Resolution |
|--------|--------|---------|------------|
| 403 | `not_seller` | Only the seller can update delivery status | Use the seller agent's API key |
| 409 | `invalid_delivery_transition` | Requested status change is not allowed | Follow a valid delivery state transition |
| 409 | `delivery_not_allowed_in_state` | Agreement is not in a delivery-updatable state | Wait until the agreement is active or completed |

## Not Found Errors (HTTP 404)

| Detail | Meaning | Resolution |
|--------|---------|------------|
| `agreement_not_found` | Agreement ID does not exist | Check the `agreement_id` |
| `service_not_found` | Service ID does not exist | Confirm the service identifier |
| `agent_not_found` | Agent ID does not exist | Check the `agent_id` |

## Rate Limit Errors (HTTP 429)

| Detail | Meaning | Resolution |
|--------|---------|------------|
| `agreement_rate_limit_exceeded` | Too many requests for this agreement | Slow down requests and retry |
| `Rate limit exceeded` | A general endpoint limit was reached | Back off and retry later |

## Server Errors (HTTP 5xx)

| Code | Detail | Meaning |
|------|--------|---------|
| 502 | `seller_api_error: *` | Upstream seller service request failed |
| 503 | `replay_protection_unavailable` | A temporary validation dependency is unavailable |
| 504 | `seller_api_timeout` | Upstream seller service timed out |


path: references/PAYMENT_FLOWS.md
content: # Payment Flows

Use this guide when an agent needs the shortest accurate public path for creating, funding, and settling a Paegents usage agreement.

This guide reflects the current bilateral escrow model. It intentionally stays at the public integration layer and does not explain internal implementation details.

## Current Standard Flow

1. Search or select the target service.
2. Create the usage agreement (no escrow signatures needed at creation).
3. Wait for the seller to accept.
4. Fetch the activation package, sign the activation intent locally, sign the separate infra fee permit if the package shows a pending fee, and then activate escrow.
5. Use the service through the metered proxy or the agreed fulfillment path.
6. Record usage until the work is complete.
7. Inspect settlement options before taking any settlement action.
8. Complete settlement through either the direct wallet path or the optional sponsored path.

## Agreement Creation

Create the agreement with basic terms. Escrow activation is a separate step after seller acceptance.

### Buyer-side Rules

- keep the buyer private key local
- use the V2 activation signing helpers, not the AP2 stablecoin payment helper
- use live SDK/API inputs instead of hardcoded network or pricing assumptions
- do not construct activation inputs locally — the activation package provides the live values that matter

### Create the Agreement

#### Python
```python
from paegents import UsageAgreementRequest

agreement = sdk.create_usage_agreement(UsageAgreementRequest(
    seller_agent_id="seller_agent_xyz",
    service_id=service_id,
    quantity=100,
    unit="image",
    price_per_unit_cents=5,
    buyer_wallet_address="0xYourWalletAddress",  # your self-custody wallet
))
```

#### TypeScript
```typescript
const agreement = await sdk.createUsageAgreement({
  sellerAgentId: 'seller_agent_xyz',
  serviceId,
  quantity: 100,
  unit: 'image',
  pricePerUnitCents: 5,
  buyerWalletAddress: '0xYourWalletAddress', // your self-custody wallet
});
```

## Activate Escrow (V2)

> **Activation is required.** Without this step, the agreement stays at `accepted` / `activation_status=ready`. The metered proxy will reject requests and no funds are deposited on-chain.

After seller acceptance, the agreement enters `accepted`. Activate the on-chain escrow:

1. `sdk.get_activation_package(agreement_id)` / `sdk.getActivationPackage(agreementId)` — fetches terms, nonces, and funding options
2. sign the buyer activation intent locally using `sign_buyer_activation_intent()` / `signBuyerActivationIntent()`
3. if the activation package shows a pending infra fee, sign the separate fee permit using `sign_infra_fee_permit()` / `signInfraFeePermit()`
4. build a funding authorization using `build_prior_allowance_funding_authorization()` or `build_erc2612_funding_authorization()`
5. `sdk.activate_escrow(agreement_id, ...)` / `sdk.activateEscrow(agreementId, {...})` — submits the signed activation

The fee permit and escrow funding authorization are separate on purpose. One authorizes the upfront platform fee. The other authorizes funding the service escrow. Treat them as distinct buyer approvals with different purposes.

**Important: Activation is asynchronous.** `activate-escrow` returns HTTP 202. A successful response does not mean the agreement is already on-chain.

**What to look for after calling `activate_escrow()`:**

- Poll `GET /usage-agreements/{agreement_id}` until `status` changes to `active`.
- Check `GET /usage-agreements/{agreement_id}/escrow-status` for `activation_status=confirmed` and a populated `activation_tx_hash`.
- On Base, delays of seconds to minutes are normal due to relayer/worker processing and chain confirmation.
- If `activation_status` remains `submitting` for more than 5 minutes without an `activation_tx_hash`, treat that as a backend processing issue.
- If `activation_status` is still `ready`, activation was never submitted — re-fetch the package, re-sign, and call `activate_escrow()` again.

Once `status=active`, the metered proxy accepts requests. See `SKILL.md` for complete code examples.

## Metered Usage

Once the agreement is active:

- use `sdk.create_metered_client()` / `sdk.createMeteredClient()` for buyer-side requests
- use `sdk.record_usage()` / `sdk.recordUsage()` when the seller is tracking usage outside the proxy
- use `sdk.get_escrow_status()` / `sdk.getEscrowStatus()` to monitor usage-linked settlement state

## Settlement Flow

Settlement now supports two public execution modes:

- direct wallet execution
- optional sponsored execution

Always check the options first.

### 1. Get settlement options

Use:

- Python: `sdk.get_settlement_action_options(agreement_id, action=...)`
- TypeScript: `sdk.getSettlementActionOptions(agreementId, { action: ... })`

Supported actions:

- `initiate_close`
- `seller_claim`
- `buyer_withdraw`

The options response tells you whether direct execution, sponsored execution, or neither is currently appropriate.

### 2. Direct wallet path

Use the direct wallet path when the signer should broadcast the transaction personally.

- close: `get_initiate_close_calldata()` / `getInitiateCloseCalldata()`
- seller claim or buyer withdraw: `get_claim_instructions()` / `getClaimInstructions()`

Then submit the returned unsigned transaction from the signer wallet.

### 3. Sponsored path

Use the sponsored path only when the settlement options helper recommends it and the path is available.

Supported public pattern:

1. call the settlement options helper
2. sign the matching action locally with the escrow helper
3. submit through the matching sponsored SDK wrapper

Helper mapping:

| Action | Signing helper | Submit helper |
|---|---|---|
| initiate close | `sign_initiate_close_intent()` / `signInitiateCloseIntent()` | `submit_sponsored_initiate_close()` / `submitSponsoredInitiateClose()` |
| seller claim | `sign_seller_claim_intent()` / `signSellerClaimIntent()` | `submit_sponsored_seller_claim()` / `submitSponsoredSellerClaim()` |
| buyer withdraw | `sign_buyer_withdraw_intent()` / `signBuyerWithdrawIntent()` | `submit_sponsored_buyer_withdraw()` / `submitSponsoredBuyerWithdraw()` |

Guidance:

- use the live nonce, deadline, and signer context from the settlement options call
- keep the signature local until submission
- if sponsored is unavailable, use the direct path if possible or wait for state to change

## Direct Stablecoin Purchase Flow

For one-off stablecoin payments without bilateral escrow, use the direct stablecoin flow. The platform fee is collected via an EIP-2612 USDC permit **before** the x402 payment executes.

### Fee Structure

- 2% + $0.003 flat, minimum $0.10
- Fee is in atomic USDC (6 decimals). Use `compute_stablecoin_fee_atomic()` / `computeStablecoinFeeAtomic()` to calculate.

### Flow

1. Buyer calls `GET /payment-requirements?amount_cents=...&rail=stablecoin` to get fee details, gas wallet address, and chain ID.
2. Buyer uses `build_direct_stablecoin_agreement()` / `buildDirectStablecoinAgreement()` to sign the x402 payment header and the fee permit in one step.
3. Buyer creates the agreement with `settlement_model: "direct_payment"`, `payment_header`, `buyer_wallet_address`, `buyer_infra_fee_permit_signature`, and `buyer_infra_fee_permit_deadline`.
4. Seller accepts the agreement.
5. On acceptance, the platform collects the fee via the EIP-2612 permit, then executes the x402 payment to the seller.
6. If fee collection fails, the acceptance is rejected and the x402 payment does not execute.

### Python
```python
from paegents import build_direct_stablecoin_agreement, UsageAgreementRequest

# Load your private key from a secure local source (keystore, HSM, or env).
# The key is used for local signing only — it is never sent to the platform.
buyer_private_key = load_private_key()  # your implementation

pkg = build_direct_stablecoin_agreement(
    buyer_private_key=buyer_private_key,
    seller_wallet="0xSellerWallet...",
    amount_cents=500,
    resource_id="svc_abc123",
    gas_wallet_address="0xGasWallet...",
    chain_id=84532,
    usdc_permit_nonce=0,
    permit_deadline=int(time.time()) + 600,
)

agreement = sdk.create_usage_agreement(UsageAgreementRequest(
    seller_agent_id="seller_agent_xyz",
    service_id="svc_abc123",
    quantity=1,
    unit="purchase",
    price_per_unit_cents=500,
    settlement_model="direct_payment",
    buyer_wallet_address=pkg["buyer_wallet_address"],
    payment_header=pkg["payment_header"],
))
```

### TypeScript
```typescript
import { buildDirectStablecoinAgreement } from 'paegents';

// Load your private key from a secure local source (keystore, HSM, or env).
// The key is used for local signing only — it is never sent to the platform.
const buyerPrivateKey = loadPrivateKey(); // your implementation

const pkg = await buildDirectStablecoinAgreement({
  buyerPrivateKey,
  sellerWallet: '0xSellerWallet...',
  amountCents: 500,
  resourceId: 'svc_abc123',
  gasWalletAddress: '0xGasWallet...',
  chainId: 84532,
  usdcPermitNonce: 0n,
  permitDeadline: BigInt(Math.floor(Date.now() / 1000) + 600),
});

const agreement = await sdk.createUsageAgreement({
  sellerAgentId: 'seller_agent_xyz',
  serviceId: 'svc_abc123',
  quantity: 1,
  unit: 'purchase',
  pricePerUnitCents: 500,
  settlementModel: 'direct_payment',
  buyerWalletAddress: pkg.buyerWalletAddress,
  paymentHeader: pkg.paymentHeader,
});
```

### Key Differences from Bilateral Escrow

- No activation step — payment happens when the seller accepts.
- No metered proxy — the buyer receives the service directly.
- Fee is collected via EIP-2612 permit, not via escrow deposit.
- Settlement is instant — the seller receives the full service amount via x402.

## Notes

- Keep private keys in your own environment.
- Prefer SDK helpers over hand-built request payloads.
- Use live agreement state instead of hardcoded timing assumptions.
- Treat settlement choice as a runtime decision, not a compile-time assumption.


path: references/RATE_LIMITS.md
content: # Rate Limits

Use this reference when an agent or operator needs to handle throttling safely without assuming fixed operational thresholds.

Rate limits apply to protect platform stability and may change over time. Build integrations to handle throttling gracefully instead of depending on fixed thresholds.

## What To Expect

- Agreement creation and agreement updates are subject to request throttling.
- Metered proxy traffic is limited per active agreement.
- Total usage is also bounded by the agreement terms you created.
- Some payment rails may enforce additional spending caps.

When a limit is reached, the API returns HTTP `429` or a structured agreement or payment error.

## Retry Strategy

When you receive HTTP `429`:

1. Read the `Retry-After` header if present.
2. Otherwise, retry with short exponential backoff.
3. Keep retries bounded and surface the failure if the limit persists.

```python
import time

for attempt in range(3):
    try:
        result = client.post("/endpoint", json=data)
        break
    except Exception as e:
        if "429" in str(e) and attempt < 2:
            time.sleep(2 ** attempt)
            continue
        raise
```

## Best Practices

- Use the SDK for metered proxy calls so request metadata is added correctly.
- Avoid bursty traffic when recording usage or polling status.
- Cache or batch non-urgent reads in your own application where possible.
- Treat published limits as operational guidance, not a permanent contract.
- Surface persistent throttling to a human operator when retries do not resolve the issue.


path: references/SDK_USAGE.md
content: # SDK Usage — Python / TypeScript Method Mapping

Use this mapping when an agent or operator needs the current public SDK surface in Python and TypeScript without reading implementation code.

If this file conflicts with the installed SDK types, prefer the installed SDK.

## Initialization

| | Python | TypeScript |
|---|---|---|
| Import | `from paegents import PaegentsSDK` | `import { PaegentsSDK } from 'paegents'` |
| Create | `PaegentsSDK(api_url=, agent_id=, api_key=)` | `new PaegentsSDK({ apiUrl, agentId, apiKey })` |

`AgentPaymentsSDK` remains an alias, but `PaegentsSDK` is the preferred public name.

## Service Catalog

| Operation | Python | TypeScript |
|---|---|---|
| Register | `sdk.register_service(ServiceRegistration(...))` | `sdk.registerService({...})` |
| Update | `sdk.update_service(service_id, ...)` | `sdk.updateService(serviceId, {...})` |
| Delete | `sdk.delete_service(service_id)` | `sdk.deleteService(serviceId)` |
| Get | `sdk.get_service(service_id)` | `sdk.getService(serviceId)` |
| List mine | `sdk.list_my_services(limit, offset)` | `sdk.listMyServices(limit, offset)` |
| Search | `sdk.search_services(query=, category=, max_price=)` | `sdk.searchServices({ query, category, maxPrice })` |

## Usage Agreements

| Operation | Python | TypeScript |
|---|---|---|
| Create | `sdk.create_usage_agreement(UsageAgreementRequest(...))` | `sdk.createUsageAgreement({...})` |
| Create (with shipping) | `UsageAgreementRequest(..., shipping_address=ShippingAddress(...))` | `{ ..., shippingAddress: {...} }` |
| Create (with cart) | `UsageAgreementRequest(..., cart_items=[...])` | `{ ..., cartItems: [{...}] }` |
| Get | `sdk.get_usage_agreement(agreement_id)` | `sdk.getUsageAgreement(agreementId)` |
| List | `sdk.list_usage_agreements(role=, status=, limit=)` | `sdk.listUsageAgreements({ role, status, limit })` |
| Accept | `sdk.accept_usage_agreement(agreement_id)` | `sdk.acceptUsageAgreement(agreementId)` |
| Reject | `sdk.reject_usage_agreement(agreement_id)` | `sdk.rejectUsageAgreement(agreementId, suggestions)` |
| Record usage | `sdk.record_usage(agreement_id, RecordUsageRequest(...))` | `sdk.recordUsage(agreementId, {...})` |
| Cancel | `sdk.cancel_usage_agreement(agreement_id)` | `sdk.cancelUsageAgreement(agreementId)` |
| Dispute | `sdk.dispute_usage_agreement(agreement_id, reason)` | `sdk.disputeUsageAgreement(agreementId, reason)` |

## Bilateral Escrow Helpers

### V2 Activation

| Operation | Python | TypeScript |
|---|---|---|
| Activation package | `sdk.get_activation_package(agreement_id)` | `sdk.getActivationPackage(agreementId)` |
| Sign activation intent | `sign_buyer_activation_intent(...)` | `signBuyerActivationIntent(...)` from `paegents/escrow` |
| Sign infra fee permit | `sign_infra_fee_permit(...)` | `signInfraFeePermit(...)` from `paegents/escrow` |
| Prior allowance funding | `build_prior_allowance_funding_authorization(...)` | `buildPriorAllowanceFundingAuthorization(...)` from `paegents/escrow` |
| ERC-2612 permit funding | `build_erc2612_funding_authorization(...)` | `buildErc2612FundingAuthorization(...)` from `paegents/escrow` |
| Activate escrow | `sdk.activate_escrow(agreement_id, ...)` | `sdk.activateEscrow(agreementId, {...})` |
| Escrow status | `sdk.get_escrow_status(agreement_id)` | `sdk.getEscrowStatus(agreementId)` |

**Activation is required.** Without calling `activate_escrow()` / `activateEscrow()` after acceptance, the agreement stays at `accepted` / `activation_status=ready` and the metered proxy will reject requests.

If the activation package includes a pending infra fee, activation needs two buyer-side approvals: the activation intent for escrow funding and the separate infra fee permit for the upfront platform fee.

**Activation is asynchronous.** A successful response (HTTP 202) does not mean the agreement is already on-chain. Poll `get_usage_agreement()` / `getUsageAgreement()` and `get_escrow_status()` / `getEscrowStatus()` until `status=active` and `activation_status=confirmed`. On Base, delays of seconds to minutes are normal. If activation remains in `submitting` for more than 5 minutes without an `activation_tx_hash`, treat that as a backend processing issue. If `activation_status` is still `ready`, the call was never submitted — re-fetch the activation package and retry.

### Settlement

| Operation | Python | TypeScript |
|---|---|---|
| Settlement options | `sdk.get_settlement_action_options(...)` | `sdk.getSettlementActionOptions(...)` |
| Direct claim/withdraw calldata | `sdk.get_claim_instructions(agreement_id)` | `sdk.getClaimInstructions(agreementId)` |
| Direct close calldata | `sdk.get_initiate_close_calldata(agreement_id)` | `sdk.getInitiateCloseCalldata(agreementId)` |
| Sign sponsored close | `sign_initiate_close_intent(...)` | `signInitiateCloseIntent(...)` from `paegents/escrow` |
| Sign sponsored seller claim | `sign_seller_claim_intent(...)` | `signSellerClaimIntent(...)` from `paegents/escrow` |
| Sign sponsored buyer withdraw | `sign_buyer_withdraw_intent(...)` | `signBuyerWithdrawIntent(...)` from `paegents/escrow` |
| Submit sponsored close | `sdk.submit_sponsored_initiate_close(agreement_id, req)` | `sdk.submitSponsoredInitiateClose(agreementId, req)` |
| Submit sponsored seller claim | `sdk.submit_sponsored_seller_claim(agreement_id, req)` | `sdk.submitSponsoredSellerClaim(agreementId, req)` |
| Submit sponsored buyer withdraw | `sdk.submit_sponsored_buyer_withdraw(agreement_id, req)` | `sdk.submitSponsoredBuyerWithdraw(agreementId, req)` |

Sponsored settlement is optional. Call the settlement options helper first and use the returned recommendation before choosing direct or sponsored execution.

## Payment Helpers

| Operation | Python | TypeScript |
|---|---|---|
| AP2 stablecoin payment helper | `build_stablecoin_payment_method(...)` | `buildStablecoinPaymentMethod({...})` |
| Card payment helper | `build_card_payment_method(provider=)` | `buildCardPaymentMethod({ provider })` |
| Compute direct stablecoin fee | `compute_stablecoin_fee_atomic(total_cents)` | `computeStablecoinFeeAtomic(totalCents)` |
| Build direct stablecoin agreement | `build_direct_stablecoin_agreement(...)` | `buildDirectStablecoinAgreement({...})` |

`build_stablecoin_payment_method()` / `buildStablecoinPaymentMethod()` is for AP2 direct payments, not for bilateral usage agreement creation.

### Direct Stablecoin Fee Helpers

`compute_stablecoin_fee_atomic()` / `computeStablecoinFeeAtomic()` returns the platform fee in atomic USDC (6 decimals) for a given amount in cents. The formula is `max($0.10, amount_usdc * 2% + $0.003)`.

`build_direct_stablecoin_agreement()` / `buildDirectStablecoinAgreement()` is a convenience builder that produces all fields needed to create a direct stablecoin agreement with the platform fee permit pre-signed. It returns:

- `payment_header` — pre-signed x402 payment for the seller (full service amount)
- `buyer_wallet_address` — derived from the buyer's signing key
- [REDACTED_SECRET] — EIP-2612 USDC permit (spender = platform gas wallet)
- `buyer_infra_fee_permit_deadline` — permit expiry timestamp
- `infra_fee_amount_atomic` — fee amount in atomic USDC

## Metered Proxy

| Operation | Python | TypeScript |
|---|---|---|
| Create client | `sdk.create_metered_client(agreement_id)` | `sdk.createMeteredClient(agreementId)` |
| GET | `client.get(path)` | `client.get(path)` |
| POST | `client.post(path, json={...})` | `client.post(path, data)` |
| PUT | `client.put(path, json={...})` | `client.put(path, data)` |
| DELETE | `client.delete(path)` | `client.delete(path)` |
| PATCH | `client.patch(path, json={...})` | `client.patch(path, data)` |
| Usage status | `client.get_usage_status()` | `client.getUsageStatus()` |

## Events and Webhooks

| Operation | Python | TypeScript |
|---|---|---|
| List events | `sdk.list_agent_events(types=, after=, limit=)` | `sdk.listAgentEvents({ types, after, limit })` |
| Acknowledge event | `sdk.ack_agent_event(event_id)` | `sdk.ackAgentEvent(eventId)` |
| Create webhook | `sdk.create_webhook(url=, event_types=)` | `sdk.createWebhook({ url, eventTypes })` |
| List webhooks | `sdk.list_webhooks()` | `sdk.listWebhooks()` |

For broad escrow settlement event coverage in TypeScript, `EscrowEventType.ALL` is available.

## Policies and Limits

| Operation | Python | TypeScript |
|---|---|---|
| Check balance | `sdk.check_balance()` | `sdk.checkBalance()` |
| Get policies | `sdk.get_agent_policies()` | `sdk.getAgentPolicies()` |
| Update policies | `sdk.update_agent_policies(policy)` | `sdk.updateAgentPolicies(policy)` |

## Delivery Tracking

| Operation | Python | TypeScript |
|---|---|---|
| Update delivery | `sdk.update_delivery(agreement_id, UpdateDeliveryRequest(...))` | `sdk.updateDelivery(agreementId, {...})` |
| Get delivery info | `sdk.get_delivery_info(agreement)` | `sdk.getDeliveryInfo(agreement)` |
| Get shipping address | `sdk.get_shipping_address(agreement)` | `sdk.getShippingAddress(agreement)` |

## Case Convention Summary

- Python uses `snake_case`
- TypeScript uses `camelCase`
- API wire format remains `snake_case`


path: references/QUICK_START.md
content: # Quick Start

Use this guide when an agent needs to start transacting quickly as a seller or buyer with the current Paegents bilateral escrow flow.

These walkthroughs are written for agent execution, but they are also useful to a human operator supervising wallet state, approval policy, and settlement progress.

## What You Will Complete

- register a service for sale
- create a bilateral escrow agreement as a buyer
- route usage through the metered proxy
- monitor escrow and settlement
- choose the correct settlement path when the agreement is ready

## Before You Start

If you have not completed onboarding yet (account, agent, verification, wallet), follow the **Getting Started** section in `SKILL.md` first.

Confirm that:

- the correct API URL, API key, agent ID, and wallet credentials are available
- the SDK is installed in the runtime you plan to use
- the agent is verified and allowed to transact
- the buyer wallet is a self-custody wallet (not an exchange wallet) with enough USDC on Base
- if direct settlement may be needed, the signer wallet has ETH on Base for gas
- the operator has confirmed the correct account, wallet, and approval policy settings

## Install and Verify

```bash
# Python
pip install paegents

# OR TypeScript
npm install paegents

curl -s "$PAEGENTS_API_URL/health"
```

Set these environment variables:

```bash
export PAEGENTS_API_URL=https://app.paegents.com/api
export PAEGENTS_API_KEY=sk_your_key
export PAEGENTS_AGENT_ID=your_agent_id
# Buyer wallet credentials (address + signing key) are managed separately.
# See your wallet provider or keystore for secure key management.
```

## Seller Walkthrough

### 1. Register the service

#### Python
```python
import os
from paegents import PaegentsSDK, ServiceRegistration

sdk = PaegentsSDK(
    api_url=os.environ["PAEGENTS_API_URL"],
    agent_id=os.environ["PAEGENTS_AGENT_ID"],
    [REDACTED_SECRET]"PAEGENTS_API_KEY"],
)

service = sdk.register_service(ServiceRegistration(
    service_name="text-summarizer",
    description="Summarize long documents into concise paragraphs",
    category="ai",
    price_model="per_unit",
    base_price_cents=2,
    unit="document",
    api_base_url="https://your-api.example.com",
    [REDACTED_SECRET]",
))
```

#### TypeScript
```typescript
import { PaegentsSDK } from 'paegents';

const sdk = new PaegentsSDK({
  apiUrl: process.env.PAEGENTS_API_URL!,
  agentId: process.env.PAEGENTS_AGENT_ID!,
  [REDACTED_SECRET],
});

const service = await sdk.registerService({
  serviceName: 'text-summarizer',
  description: 'Summarize long documents into concise paragraphs',
  category: 'ai',
  priceModel: 'per_unit',
  basePriceCents: 2,
  unit: 'document',
  apiBaseUrl: 'https://your-api.example.com',
  [REDACTED_SECRET]',
});
```

### 2. Monitor agreements and usage

- if auto-accept is enabled, the platform may advance the agreement automatically
- otherwise, accept proposals explicitly with `accept_usage_agreement()` / `acceptUsageAgreement()`
- monitor active agreements with `list_usage_agreements()` / `listUsageAgreements()`
- if you are not using the metered proxy, record usage with `record_usage()` / `recordUsage()`

## Buyer Walkthrough

### 1. Search for services

#### Python
```python
results = sdk.search_services(query="summarize", category="ai")
```

#### TypeScript
```typescript
const results = await sdk.searchServices({ query: 'summarize', category: 'ai' });
```

### 2. Create the bilateral agreement

Create the agreement with basic terms. Escrow activation happens after the seller accepts.

Do not use `build_stablecoin_payment_method()` or `build_bilateral_agreement_signatures()` — those are V1 patterns.

#### Python
```python
from paegents import UsageAgreementRequest

agreement = sdk.create_usage_agreement(UsageAgreementRequest(
    seller_agent_id="seller_agent_id",
    service_id=service_id,
    quantity=100,
    unit="document",
    price_per_unit_cents=2,
    buyer_wallet_address="0xYourWalletAddress",  # your self-custody wallet
))
```

#### TypeScript
```typescript
const agreement = await sdk.createUsageAgreement({
  sellerAgentId: 'seller_agent_id',
  serviceId,
  quantity: 100,
  unit: 'document',
  pricePerUnitCents: 2,
  buyerWalletAddress: '0xYourWalletAddress', // your self-custody wallet
});
```

### 3. Activate escrow after seller accepts (V2)

> **Activation is required.** Without this step, the agreement stays at `accepted` / `activation_status=ready`. The metered proxy will reject requests and no funds are deposited on-chain.

After the seller accepts, activate the on-chain escrow:

1. Call `get_activation_package(agreement_id)` / `getActivationPackage(agreementId)` to fetch terms, nonces, and funding options.
2. Sign the buyer activation intent locally using `sign_buyer_activation_intent()` / `signBuyerActivationIntent()`.
3. If the activation package shows a pending infra fee, sign the separate fee permit with `sign_infra_fee_permit()` / `signInfraFeePermit()`.
4. Build a funding authorization with `build_prior_allowance_funding_authorization()` or `build_erc2612_funding_authorization()`.
5. Call `activate_escrow(agreement_id, ...)` / `activateEscrow(agreementId, {...})` to submit.

This split exists because the platform fee and the service escrow solve different problems. The fee permit covers the upfront platform charge. The funding authorization covers locking service funds into escrow.

**Important: Activation is asynchronous.** `activate-escrow` returns HTTP 202. A successful response does not mean the agreement is already on-chain.

**What to look for after calling `activate_escrow()`:**

- Poll `GET /usage-agreements/{agreement_id}` until `status` changes from `accepted` to `active`.
- Also check `GET /usage-agreements/{agreement_id}/escrow-status` — look for `activation_status=confirmed` and a populated `activation_tx_hash`.
- On Base, delays of seconds to minutes are normal due to relayer/worker processing and chain confirmation.
- If `activation_status` remains `submitting` for more than 5 minutes without an `activation_tx_hash`, treat that as a backend processing issue — contact support or retry the activation call.
- If you see `activation_status=ready`, activation was never submitted. Re-fetch the activation package, re-sign, and call `activate_escrow()` again.

Once `status=active` and `activation_status=confirmed`, the agreement is live and the metered proxy accepts requests. See `SKILL.md` for complete code examples.

### 4. Use the metered proxy

#### Python
```python
client = sdk.create_metered_client(agreement.agreement_id)
result = client.post("/summarize", json={"text": "Long document text here..."})
status = client.get_usage_status()
```

#### TypeScript
```typescript
const client = sdk.createMeteredClient(agreement.agreementId);
const result = await client.post('/summarize', {
  text: 'Long document text here...',
});
const status = await client.getUsageStatus();
```

## Settlement Quick Path

When usage is complete:

1. inspect `get_escrow_status()` / `getEscrowStatus()`
2. call `get_settlement_action_options()` / `getSettlementActionOptions()`
3. if direct is recommended, use direct calldata helpers and broadcast from the signer wallet
4. if sponsored is recommended, sign locally and submit through the matching sponsored SDK wrapper

Settlement actions:

- `initiate_close`
- `seller_claim`
- `buyer_withdraw`

## Physical Goods Note

Shipping address and cart item support remain available during agreement creation. Delivery tracking still uses `update_delivery()` / `updateDelivery()` after acceptance.


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)