{
  "schema": "aaes/capability-pack/1",
  "provider": "coupa",
  "version": "1.0.0",
  "description": "Coupa core REST API on the customer's own instance: read suppliers, invoices, purchase orders, requisitions and approvals, and perform the documented approve action on a pending approval.",
  "notes": "The endpoint host 'coupa-instance.invalid' is a reserved non-production placeholder (an RFC 2606 reserved TLD): substitute the customer's own Coupa instance host, whose documented form is {customer_name}.coupahost.com ({organization_name}.coupacloud.com for partner and demo instances), before enabling, in every endpoint, in every resource locator and in every egress allowlist entry. The allowlist matches exactly, so endpoints and allowlist must be substituted together, and substitution is a required deployment step — the reserved host being allowlisted does not make an unsubstituted pack safe to enable.\n\nThis pack registers only endpoints the Coupa Compass documentation for the Coupa core API documents: the index (collection) reads on /api/suppliers, /api/invoices, /api/purchase_orders, /api/requisitions and /api/approvals, and the documented approval action PUT /api/approvals/{id}/approve. All reads are collection reads only: single-record show reads (GET /api/suppliers/{id} and the equivalent for the other resources) exist in the API and are deliberately not registered.\n\nExcluded operations: every create, update and delete on suppliers, invoices, purchase orders and requisitions; requisition submission (POST /api/requisitions/submit_for_approval and PUT /api/requisitions/{id}/update_and_submit_for_approval); the approval reject and hold actions (PUT /api/approvals/{id}/reject, PUT /api/approvals/{id}/hold); approval update (PUT /api/approvals/{id}); purchase order transmission and Coupa Pay functions; and every other Coupa API. The approve capability is registered as approve at the minimum R3 tier — approve is one of the designated actions requiring human approval.\n\nOn supported AAES-mediated execution paths, irreversible actions require approval by an authorized person; this does not prevent calls through bypass or observation-only paths. Effect-level precision for the approve call: Coupa documents it as performing the approve action on an approval that must be pending, and a successful call records the approver's judgement and advances the document's approval chain.\n\nThe call itself moves no money, but completing a document's approval chain can trigger the customer's configured downstream procurement and payment workflows (an approved requisition can become purchase orders; an approved invoice can become payable and be paid, including through Coupa Pay where configured): HTTP 200 confirms the approval was recorded, and this pack not registering payment endpoints does not prove those configured workflows cannot schedule payment.\n\nCoupa's older X-COUPA-API-KEY scheme is documented in older Compass pages alongside the OAuth transition guide; this pack registers only the current OAuth 2.0 client-credentials scheme, whose documented injection point is the Authorization header.",
  "credential": {
    "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
    "custody": "customer_vault",
    "inject": "header",
    "inject_name": "Authorization",
    "note": "An OAuth 2.0 access token for the customer's own Coupa instance, held in the client's own secret manager. The stored material must be the complete Authorization header value (scheme included): 'Bearer', one separating space, then the access token — 'Bearer <token>'. The token is obtained with the client credentials grant: an integrations-enabled administrator creates an OAuth2/OIDC client (Setup > Oauth2/OpenID Connect Clients) with grant type Client credentials, and an external deployment process POSTs client_id, client_secret, grant_type=client_credentials and the selected scopes to https://<instance>/oauth2/token and stores the returned access token; the client secret is itself secret material and stays with that external process.\n\nThe executor injects the stored value verbatim and performs no token lifecycle itself. customer_vault is the only generally-supported production custody model here — identity_federation and customer_broker are refused at startup and inline is lab-only.\n\nExpiry and revocation: Coupa documents the access token as lasting 24 hours (expires_in 86399 seconds) and recommends renewing it every 20 hours; if the token lapses the external process must make a new token call — the executor must not attempt renewal itself. The OAuth client can be deactivated, deleted or rescoped by an administrator at any time (Coupa documents activate/deactivate per client on the OpenID Connect Clients page), which stops future token issuance for that client and changes what subsequently issued tokens may do; disabling issuance and changing scopes are different events from invalidating an already-issued token, and Coupa's documentation does not describe client deactivation, deletion or rescoping as invalidating a 24-hour access token already issued before its natural expiry.\n\nAn expired value fails closed.\n\nLeast privilege: Coupa scopes take the documented form service.object.right (for example core.accounting.read), and the authoritative list of scopes and their underlying permissions is the Scope management page on the customer's own instance (/oauth2/scopes) — verify membership there before enabling, because published third-party enumerations may lag Coupa's own list. Grant the client only the read scopes for suppliers, invoices, purchase orders, requisitions and approvals, plus the approval write scope for the approve capability, and nothing more.\n\nResidual-authority disclosure: third-party integration documentation that enumerates the Coupa scopes (Rossum's Coupa OAuth scopes documentation, retrieved 2026-09-21; the ServiceNow Coupa integration-profile reference yielded only a JavaScript shell, so it did not corroborate that scope mapping) shows the approval write scope core.approval.write covering the Approve, Create, Hold, Reject and Update approval operations together — approve, reject and hold share one write scope — so a client holding the approval write scope necessarily holds provider-side permission for the reject, hold, create and update approval actions this pack deliberately does not register.\n\nThat residual authority is a provider permission on the client, distinct from what AAES registers (only PUT /api/approvals/{id}/approve is registered); AAES non-registration does not reduce the client's Coupa-side grant.\n\nExecutor hygiene: the value must live only in executor memory for the lifetime of a call, must never be written to logs, traces or diagnostics, and must not appear in crash dumps; a deployment that cannot meet that bar must not enable the pack."
  },
  "capabilities": [
    {
      "capability_id": "coupa.suppliers.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "pii",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/suppliers",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "coupa-suppliers",
      "description": "Query the suppliers collection: supplier names, numbers, statuses, addresses, primary contact emails, DUNS and tax identifiers — supplier records carry named contacts and can identify individual persons, so the read is classified as pii (personal data) (a deployment whose policy classifies supplier master data differently may change it).\n\nThe registered path covers the collection read only — single-supplier reads (GET /api/suppliers/{id}) and child resources (remit-to addresses, supplier addresses) are not registered by this pack.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "coupa.invoices.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/invoices",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "coupa-invoices",
      "description": "Query the invoices collection: invoice headers with suppliers, totals, currencies, payment terms, payment channels and statuses, classified as financial data.\n\nThe registered path covers the collection read only — single-invoice reads (GET /api/invoices/{id}), invoice lines, image scans and clearance documents are not registered by this pack.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "coupa.purchaseorders.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/purchase_orders",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "coupa-purchaseorders",
      "description": "Query the purchase orders collection: order headers with suppliers, requesters, currencies, totals, payment terms and transmission statuses, classified as financial data.\n\nThe registered path covers the collection read only — single-order reads (GET /api/purchase_orders/{id}) and the purchase order lines API are not registered by this pack.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "coupa.requisitions.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/requisitions",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "coupa-requisitions",
      "description": "Query the requisitions collection: requisition headers with requesters, departments, currencies, totals and statuses, classified as financial data.\n\nThe registered path covers the collection read only — single-requisition reads (GET /api/requisitions/{id}), /api/requisitions/mine and /api/requisitions/current_cart are not registered by this pack.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "coupa.approvals.read",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R0",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/approvals",
      "method": "GET",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "read"
      ],
      "resource_id": "coupa-approvals",
      "description": "Query the approvals collection: approval objects with the approvable document type and id, the approver's identity, position in the chain and status, classified as financial data because approval objects sit on financial documents and carry approver identities.\n\nThe registered path covers the collection read only — single-approval reads (GET /api/approvals/{id}) are not registered by this pack.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    },
    {
      "capability_id": "coupa.approvals.approve",
      "version": "1.0.0",
      "kind": "tool",
      "risk_tier": "R3",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN",
      "data_class": "financial",
      "egress_allowlist": [
        "coupa-instance.invalid"
      ],
      "endpoint": "https://coupa-instance.invalid/api/approvals/APPROVAL_ID/approve",
      "method": "PUT",
      "connector": "http",
      "custody": "customer_vault",
      "secret_ref_id": "secret-ref-coupa-oauth2-access-token",
      "inject": "header",
      "inject_name": "Authorization",
      "actions": [
        "approve"
      ],
      "resource_id": "coupa-approvals",
      "description": "Perform the documented approve action on one approval (PUT /api/approvals/{id}/approve). Coupa documents that the approval must be pending (not already approved or rejected) for the action to be valid, and that a successful request returns HTTP 200. Registered as approve at the minimum R3 tier — approve is one of the designated actions requiring human approval.\n\nOn supported AAES-mediated execution paths, irreversible actions require approval by an authorized person; this does not prevent calls through bypass or observation-only paths.\n\nOutcome: the call records the approver's judgement on the approval object and advances the document's approval chain — request-accepted and approval-recorded. The call itself moves no money, but completing a document's approval chain can trigger the customer's configured downstream procurement and payment workflows: an approved requisition can be converted into purchase orders, and an approved invoice can become payable and be paid, including through Coupa Pay where the customer has configured it. HTTP 200 confirms the approval was recorded; neither that response nor this pack's exclusion of payment endpoints proves those configured workflows cannot schedule payment. APPROVAL_ID is a placeholder the deployment binds to a concrete approval before enabling.\n\nThe host is a reserved placeholder: substitute the customer's instance host before enabling.",
      "requires_review": true
    }
  ],
  "resources": [
    {
      "resource_id": "coupa-suppliers",
      "kind": "api",
      "locator": "https://coupa-instance.invalid/api/suppliers",
      "data_class": "pii",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "coupa-invoices",
      "kind": "api",
      "locator": "https://coupa-instance.invalid/api/invoices",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "coupa-purchaseorders",
      "kind": "api",
      "locator": "https://coupa-instance.invalid/api/purchase_orders",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "coupa-requisitions",
      "kind": "api",
      "locator": "https://coupa-instance.invalid/api/requisitions",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    },
    {
      "resource_id": "coupa-approvals",
      "kind": "api",
      "locator": "https://coupa-instance.invalid/api/approvals",
      "data_class": "financial",
      "owner": "REPLACE-WITH-ACCOUNTABLE-HUMAN"
    }
  ],
  "review": {
    "state": "library",
    "date": "2026-09-22",
    "sources": [
      "https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/resources/reference-data-resources/suppliers-api-(suppliers) (retrieved 2026-09-21)",
      "https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/resources/transactional-resources/invoices-api-(invoices) (retrieved 2026-09-21)",
      "https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/resources/transactional-resources/purchase-orders-api-(purchase_orders) (retrieved 2026-09-21)",
      "https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/resources/transactional-resources/requisitions-api-(requisitions) (retrieved 2026-09-21)",
      "https://compass.coupa.com/en-us/products/product-documentation/integration-technical-documentation/the-coupa-core-api/resources/transactional-resources/approvals-api-(approvals) (retrieved 2026-09-21)",
      "https://compass.coupa.com/en-us/products/total-spend-management-platform/integration-playbooks-and-resources/integration-knowledge-articles/oauth-2.0-getting-started-with-coupa-api (retrieved 2026-09-21)",
      "https://docs.coupa.com/en/developer-documentation/the-coupa-core-api/resources/transactional-resources/approvals-api-approvals (retrieved 2026-09-21)",
      "https://docs.coupa.com/en/developer-documentation/the-coupa-core-api/oauth-2.0-and-oidc/openid-connect-clients (retrieved 2026-09-21)",
      "https://docs.coupa.com/en/developer-documentation/the-coupa-core-api/oauth-2.0-and-oidc/set-up-an-openid-connect-client (retrieved 2026-09-21)",
      "https://knowledge-base.rossum.ai/docs/oauth-20-scopes (retrieved 2026-09-21)"
    ],
    "reviewer_id": "aaes-eng"
  }
}
