Blog

From cXML OrderRequest to TMF622 — Wrapping Ariba and Coupa PunchOut Behind One Capability

Kin Lane ·May 27, 2026
Table of contents

Walk into any enterprise procurement shop and you will find the same thing. The buying side is on Ariba or Coupa. The supplier side has a PunchOut catalog wired into one or both. And the wire format that carries the order across that seam — every single time, for the last twenty years — is cXML. Specifically, the cXML OrderRequest document, with its OrderRequestHeader, its ItemOut lines, its ShipTo and BillTo parties, its Total, and its trailing payload envelope. It works. It has worked. It will keep working.

The problem is everything downstream of that seam. The ERP that books the order, the fulfillment system that picks it, the billing platform that invoices against it, the customer portal that renders a status page, and now the AI agent that a buyer is asking “where is PO 4711?” — none of them want to read cXML anymore. They want a TMF622 v5 ProductOrder. They want JSON. They want a state, a productOrderItem array, a relatedParty block, a payment, a billingAccount. And they want it from one URL, with one auth model, with one trace per call.

This is what a Naftiko capability is for.

What Naftiko is

Naftiko is an open framework for wrapping any API as a versioned capability. The capability is a single YAML file. The file declares what the capability consumes — the upstream surface in its real wire-level shape, whatever weird flavor of XML or JSON or SOAP that happens to be — and what it exposes — clean, governed surfaces on top of the upstream. Today those surfaces are REST endpoints and MCP tools for agents, from the same spec. The engine that reads the YAML runs as a container. The contract is the file. The integration disappears.

The point is to stop hand-writing the seam between legacy and modern. You name the upstream once. You name the exposed shape once. The engine handles auth, transport, transform, observability, and the agent surface. When a new downstream needs the same data in a new shape, you add an exposes block — not a new microservice.

Why TMF622 v5 for a cXML order

TM Forum’s TMF622 Product Ordering Management API v5.0 is the closest thing the industry has to a vendor-neutral ordering vocabulary. It carries everything a cXML OrderRequest carries — buyer, seller, line items, quantities, prices, delivery dates, payment terms, references — but it carries them in REST + JSON, versioned, and aligned with the rest of the TMF Open API family (TMF620 Product Catalog, TMF666 Account, TMF678 Bill, TMF679 Product Offering Qualification). If the rest of your stack is moving toward TMF — and most enterprise architectures are — every cXML order that lands on your dock has to get translated to TMF622 sooner or later.

The question is where the translation lives. Today it lives in fifteen places, each maintained by a different team, each making slightly different choices about how to map ItemOut/UnitPrice/Money to TMF622’s itemPrice.price.dutyFreeAmount. That sprawl is the actual cost. A capability collapses it to one file, one transform, one diff.

And there is a second reason. PunchOut + cXML was designed for system-to-system procurement. It assumed both sides were known, registered, certificate-pinned, and configured. None of that breaks under a capability — but you also get a TMF622 surface on top that an AI agent can call without having to learn cXML. The procurement bot does not need to know what <cXML payloadID="..."> means. It needs to know that GET /productOrder/{id} returns a ProductOrder.

What the capability looks like

The shape is straightforward. One consumes block accepts the cXML OrderRequest as the upstream POSTs it — the engine treats the inbound cXML the same way it would treat a webhook from any other source. A second consumes block represents the Ariba supplier endpoint we call back out to for status. The exposes block declares one MCP tool and one REST route per operation, and a versioned XSLT pins the cXML → TMF622 mapping in one file.

naftiko: "1.0.0-alpha2"

info:
  title: Manage Procurement Orders — cXML to TMF622
  description: >
    Accepts Ariba and Coupa PunchOut cXML OrderRequest documents at the
    door, transforms them into TMF622 v5 ProductOrder JSON, and exposes
    the result via REST and MCP. One capability, one contract, one trace.

binds:
  - namespace: env
    keys:
      ARIBA_SHARED_SECRET: ARIBA_SHARED_SECRET
      COUPA_SHARED_SECRET: COUPA_SHARED_SECRET
      ARIBA_SUPPLIER_TOKEN: ARIBA_SUPPLIER_TOKEN

capability:
  consumes:
    - namespace: cxml-inbound
      type: http
      baseUri: "https://procurement.example.com"
      description: "Inbound cXML OrderRequest endpoint  receives POSTs from Ariba and Coupa PunchOut."
      resources:
        - name: order-request
          path: "/cxml/{{buyer_id}}/orderRequest"
          operations:
            - name: receive-order-request
              method: POST
              headers:
                - name: Content-Type
                  value: "application/xml"
              inputParameters:
                - name: buyer_id
                  in: path
                  required: true
                - name: body
                  in: body
                  required: true

    - namespace: ariba
      type: http
      baseUri: "https://service.ariba.com"
      description: "Ariba supplier status endpoint for confirmation and ship notice writebacks."
      authentication:
        type: bearer
        token: "{{ARIBA_SUPPLIER_TOKEN}}"
      resources:
        - name: order-status
          path: "/api/order-confirmations/v1/{{order_id}}"
          operations:
            - name: get-ariba-order-status
              method: GET
              inputParameters:
                - name: order_id
                  in: path
                  required: true

  exposes:
    - type: mcp
      address: "0.0.0.0"
      port: 3062
      namespace: manage-procurement-orders
      description: >
        cXML OrderRequest receiver and TMF622 v5 ProductOrder reader.
        One tool to accept inbound cXML and return TMF622, one tool to
        read TMF622 by ID from the capability's cache.
      tools:
        - name: receive-cxml-as-tmf622
          description: "Accept an inbound cXML OrderRequest from Ariba or Coupa and return the equivalent TMF622 v5 ProductOrder."
          inputParameters:
            - name: buyer_id
              type: string
              required: true
              description: "Buyer org identifier  Ariba ANID or Coupa supplier-side buyer code."
            - name: body
              type: string
              required: true
              description: "Raw cXML OrderRequest document."
          call: cxml-inbound.receive-order-request
          transform:
            engine: xslt
            template: "transforms/cxml-orderrequest-to-tmf622.xsl"

        - name: get-order-status
          description: "Fetch the current Ariba order confirmation status for an order ID and return it as a TMF622 state update."
          hints:
            readOnly: true
          inputParameters:
            - name: order_id
              type: string
              required: true
          call: ariba.get-ariba-order-status
          transform:
            engine: jsonata
            template: "transforms/ariba-status-to-tmf622-state.jsonata"

    - type: rest
      address: "0.0.0.0"
      port: 8092
      namespace: manage-procurement-orders-rest
      resources:
        - name: order-request
          path: "/productOrder/cxml/{buyer_id}"
          operations:
            - name: receive-cxml-as-tmf622
              method: POST
              inputParameters:
                - name: buyer_id
                  in: path
                  required: true
                - name: body
                  in: body
                  required: true
              call: cxml-inbound.receive-order-request
              transform:
                engine: xslt
                template: "transforms/cxml-orderrequest-to-tmf622.xsl"
        - name: order-status
          path: "/productOrder/{order_id}/state"
          operations:
            - name: get-order-status
              method: GET
              inputParameters:
                - name: order_id
                  in: path
                  required: true
              call: ariba.get-ariba-order-status
              transform:
                engine: jsonata
                template: "transforms/ariba-status-to-tmf622-state.jsonata"

The cXML document hits the capability the same way it has always hit the supplier — as an HTTP POST with an XML body. The capability does not pretend cXML never existed. It accepts it on the wire, hands the body to a single XSLT, and emits a TMF622 v5 ProductOrder on the other side. OrderRequestHeader/Total/Money becomes orderTotalPrice.price.dutyFreeAmount. Each ItemOut becomes a productOrderItem with action: "add", a quantity, an itemPrice, and a productOffering reference resolved from the SupplierPartID. ShipTo/Address becomes a place with role shipToLocation. The BuyerCookie becomes the TMF622 externalId. One XSLT. One versioned contract.

The second exposes — get-order-status — closes the loop the other way. The same capability calls Ariba’s order-confirmation API and projects the status into the TMF622 state vocabulary (acknowledged, inProgress, completed, cancelled). The agent or the dashboard never sees Ariba’s status codes. It sees TMF622.

Traceable, observable, integrated into AI

Three things change the moment the cXML seam moves behind a capability.

Traceable. Every inbound OrderRequest now flows through one engine. That engine emits one trace per cXML POST: the buyer ID, the payloadID from the cXML header, the XSLT version that produced the output, the resulting TMF622 id, and the round-trip time. When procurement asks why PO 4711 looked different in billing than it did in the portal, the answer is no longer a guess across fifteen translators — it is a single trace with the cXML body on one side and the TMF622 body on the other.

Observable. The capability is one named workload. Its error rate is one metric. Its latency is one chart. If a buyer starts sending cXML with a malformed Total block, the capability’s validation failures spike in one dashboard. You do not learn about it from a quarterly reconciliation. You learn about it from the alert.

Integrated into AI. This is the move. The same MCP tool that returns a TMF622 ProductOrder to a billing system is the tool an agent calls when a buyer asks the procurement assistant “what is the status of PO 4711?” — same auth, same trace, same transform version. The agent never sees cXML. It sees TMF622. And because the MCP surface and the REST surface are declared in the same capability YAML, you cannot ship one without the other — they cannot drift. That is the whole point of declaring the contract once instead of writing the integration fifteen times.

The bigger move

Ariba and Coupa are not going away. cXML is not going away. The supplier endpoints that have spoken cXML for two decades are still going to speak cXML next year. The change is that the seam between cXML and the rest of the enterprise finally has a single owner — a capability YAML, an engine, a trace, a dashboard, and an MCP surface. That is what it looks like to wrap a twenty-year-old B2B protocol without ripping it out. And it is how every cXML OrderRequest hitting your dock starts looking like a TMF622 ProductOrder to everything downstream — including the agent the buyer is talking to.