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.