{
  "schema": "aaes/capability-pack/1",
  "provider": "xero",
  "version": "1.1.0",
  "description": "Xero: read contacts, invoices, payments and bank transactions, and create draft or approved invoices and purchase bills.",
  "notes": "The registered host api.xero.com is Xero's single shared API host, and the registered paths and methods are from Xero's official Xero-OpenAPI specification: collection GETs on /Contacts, /Invoices, /Payments and /BankTransactions, and invoice creation as PUT /Invoices — the narrower fact is that THIS PACK registers PUT creation; Xero's specification also offers POST create-or-update on the same collection, which this pack does not register. Every call also requires the xero-tenant-id header, which the same specification declares a required header; this pack registers the Authorization injection only, so the tenant id is connector or deployment configuration, set by the operator — never supplied by the agent.\n\nThe developer.xero.com documentation pages require a JavaScript browser to render; this review verified the operations, methods, scopes and required headers against the official spec instead, and the OAuth lifecycle claims against Xero's rendered OAuth 2.0 auth-flow guide. Invoice creation is declared as create plus approve at the minimum R3 tier: Xero invoices can be created in DRAFT or AUTHORISED status from the caller-controlled request body, an AUTHORISED invoice is a posted ledger document — an approved financial record, not a draft — and the registered endpoint cannot enforce a draft-only choice, so unrestricted creation on this route includes approval of the created document.\n\nAn installer's intention to request only DRAFT is not a restriction the pack can enforce. This pack does not register payment creation (see below), bank transaction creation, invoice email, attachments, history-and-notes writes, updates, or deletes — and note what a payment is here: a Xero Payment records a payment against an invoice in the ledger, a bookkeeping entry, not an instruction that moves funds; actual money movement happens through the organisation's bank outside this API.\n\nRecording such a payment is still a financial write this pack leaves out. AAES has not been certified by Xero; the endpoints here are configuration to review and test; the vendor has not endorsed it.\n\nEffect-handle equivalence for xero.invoices.create@1.1.0: effect_id_body_pointer /Invoices/0/InvoiceID makes AAES capture InvoiceID from the single invoice in the same 2xx creation response. Digest mode keeps the financial response body from the agent, the receipt digest covers the original bounded body, and effect_id_source records body.Invoices[0].InvoiceID beside the value. The body declaration is authoritative: an unexpected effect-ID header is logged as ignored and is never used or merged. Capture requires exactly one array element and a canonical UUID. A missing, malformed, over-long or non-UUID value, an oversized body, multiple invoices, or validation warnings/errors is a post-dispatch refusal requiring reconciliation, never a silent success or an automatic retry with a new key.\n\nEstablished from the reviewed OpenAPI specification: createInvoices describes its 200 response as a newly created Invoice; InvoiceID is described as a Xero-generated unique identifier with UUID format; InvoiceNumber is described as a unique alphanumeric code that is auto-generated when omitted; GET /Invoices/{InvoiceID} retrieves a specific invoice; and the required xero-tenant-id header selects the tenant. These statements establish provider assignment, UUID shape and a readback route at specification level. They do not prove identifier stability over time, uniqueness within the tenant, or live create-versus-update behavior. Stability and tenant-scoped uniqueness remain asserted properties REQUIRING sector/provider reviewer confirmation.\n\nA complete equivalence argument also requires the InvoiceID to be minted by this call. The response schema has no create-versus-update marker. If PUT updates in place when InvoiceNumber or InvoiceID matches an existing invoice, extraction alone cannot distinguish that update from creation. A Xero-specific request adapter at the gateway decision boundary must pin a fresh, client-generated, unique InvoiceNumber into the single invoice for each intent and seal that exact payload before dispatch. Asserted for this design and REQUIRING sector/provider reviewer confirmation: Xero enforces per-organisation InvoiceNumber uniqueness, so a retry with the same pinned number updates that invoice and returns the same InvoiceID. The existing scalar request contract cannot express a generated nested field, and changing the payload in the connector after the intent is sealed would break the intent binding. Pinning therefore remains outside this connector change. Until that adapter and the provider review exist, minted-by-this-call is an unproven equivalence property, not a claim made by this pack.\n\nVersioning: pack version 1.1.0 is the release in which xero.invoices.create moved to 1.1.0 (the effect_id_body_pointer declaration above); the other capabilities are unchanged since 1.0.0 and keep it, because a capability's version is the pack version in which its registration last changed.",
  "credential": {
    "secret_ref_id": "secret-ref-xero-access-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "An OAuth2 access token for the Xero Accounting API, held in the client's own secret manager under this reference. Xero issues tokens through the OAuth2 authorization-code flow (authorization endpoint login.xero.com, token endpoint identity.xero.com); a refresh token is issued only if the app requested the offline_access scope at authorization, so the external token process's authorization must include it. Per Xero's documented expiry times, the access token lives 30 minutes and the refresh token 60 days, and Xero rotates the refresh token on every refresh — the refresh response carries a new refresh token that must be saved in place of the old.\n\nExactly one external token process therefore owns the refresh path: it refreshes, stores the FRESH access token under this reference, and keeps the rotating refresh token and the app's client credentials to itself — the executor and the agent never see them, and this pack does not store one. The consequence of splitting the refresh path is real but not instantaneous: Xero documents a 30-minute grace period after each refresh during which the previous refresh token still works (so a client that lost the response can retry) — two processes refreshing independently may both succeed within that window, and invalidation follows when the grace period ends, at which point one side's token chain is dead and re-authorization is required.\n\nTreat any second refresh process as a wiring fault regardless. Revocation is documented and active: POST identity.xero.com/connect/revocation with the refresh token revokes it and removes the app's connections, and a single tenant connection can be dropped with DELETE api.xero.com/connections/{connectionId}; an already-issued access token otherwise stands until its 30-minute expiry. The stored material must be the complete Authorization header value (scheme included: 'Bearer <access-token>'). Choose the least-privilege scopes the spec names for these endpoints: accounting.contacts.read, accounting.transactions.read, and accounting.transactions for invoice creation.\n\nAt execution time the resolved bearer material exists in the executor process's memory for the moment of injection only — the deployment owns the executor's memory, log, trace and crash hygiene; the agent never receives it."
  },
  "capabilities": [
    {
      "capability_id": "xero.contacts.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "api.xero.com"
      ],
      "endpoint": "https://api.xero.com/api.xro/2.0/Contacts",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-xero-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "xero-organisation",
      "description": "Read the organisation's contacts collection: customers and suppliers with names, addresses, emails and phones, which are personal data. The collection supports where-filtering and pagination.",
      "requires_review": true
    },
    {
      "capability_id": "xero.invoices.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "api.xero.com"
      ],
      "endpoint": "https://api.xero.com/api.xro/2.0/Invoices",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-xero-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "xero-organisation",
      "description": "Read the invoices collection: sales invoices and purchase bills with line items, amounts, tax and status — financial data.",
      "requires_review": true
    },
    {
      "capability_id": "xero.payments.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "api.xero.com"
      ],
      "endpoint": "https://api.xero.com/api.xro/2.0/Payments",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-xero-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "xero-organisation",
      "description": "Read the payments collection: payments recorded against invoices and credit notes, with amounts and bank accounts — financial data.",
      "requires_review": true
    },
    {
      "capability_id": "xero.banktransactions.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "api.xero.com"
      ],
      "endpoint": "https://api.xero.com/api.xro/2.0/BankTransactions",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-xero-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "xero-organisation",
      "description": "Read the bank transactions collection: spent and received money transactions on the organisation's bank accounts — financial data.",
      "requires_review": true
    },
    {
      "capability_id": "xero.invoices.create",
      "version": "1.1.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "api.xero.com"
      ],
      "endpoint": "https://api.xero.com/api.xro/2.0/Invoices",
      "method": "PUT",
      "effect_id_body_pointer": "/Invoices/0/InvoiceID",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-xero-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "create",
        "approve"
      ],
      "resource_id": "xero-organisation",
      "description": "Create one or more invoices (this pack registers PUT creation on the collection, per Xero's OpenAPI specification).\n\nThe request body is caller-controlled and chooses the invoice status: DRAFT is an unposted working document, but AUTHORISED is a posted ledger document — approved on creation — and the endpoint cannot enforce a draft-only choice, so unrestricted creation on this route includes approving the created sales invoice or purchase bill. The actions are create plus approve at the minimum R3 tier (approve is one of the designated actions requiring human approval); enforce any draft-only restriction separately.\n\nCreation writes the document into the ledger and moves no money and sends nothing; recording a payment against it would be a financial write this pack leaves out (a Xero Payment is a ledger record, not an instruction that moves funds), and emailing an invoice would carry send at R3 — this pack registers neither.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "xero-organisation",
      "kind": "api",
      "locator": "https://api.xero.com/api.xro/2.0",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-10-22",
    "sources": [
      "https://raw.githubusercontent.com/XeroAPI/Xero-OpenAPI/master/xero_accounting.yaml (retrieved 2026-09-21)",
      "https://developer.xero.com/documentation/guides/oauth2/auth-flow/ (retrieved 2026-09-21)"
    ],
    "reviewer_id": "owner-delegated"
  }
}
