Skip to main content

cXML punchout orders

Send purchase orders from Ariba, Coupa, JAGGAER, Oracle or Workday into SpendOne as cXML OrderRequest documents, and receive order confirmations and supplier invoices back.

This page is written for the administrator of the buying organization's procurement platform. It is everything you need to configure SpendOne as a cXML supplier: the endpoint, the credential, the response contract, the fields we read, and a worked example you can copy. No SpendOne login is needed to follow it.

Direction, before anything else​

SpendOne is the supplier side of this integration. Your platform is shopped into, not out from:

  1. Your user raises and approves a requisition in your own system, under your own rules.
  2. Your platform POSTs the resulting OrderRequest to SpendOne.
  3. SpendOne executes it: the order becomes a purchase request, already approved, with a virtual payment card behind it.
  4. SpendOne confirms the outcome back to your endpoint, and can relay the supplier's invoice to you.

Most punchout material you will find describes the opposite arrangement, where a buyer punches out to a supplier's hosted catalog. SpendOne serves no catalog session. A PunchOutSetupRequest sent to the endpoint below is answered 450 Not Implemented; the only document this endpoint accepts is OrderRequest.

What you need from SpendOne first​

Your SpendOne counterpart creates a punchout connection in their SpendOne administration area and hands you these:

WhatExampleNotes
Endpoint URLhttps://api.spendone.tech/punchout/v1/tenant_acme/cxmlThe tenant id is part of the path and is fixed per customer
Shared secretso_px_...Generated by SpendOne, shown once, never retrievable afterwards. Not on SAP Business Network, where the network presents SpendOne's own secret (see below)
The identity we expect from youAN01000000123Goes in your From/Credential/Identity
The identity you address us asSPENDONE-ACMEGoes in your To/Credential/Identity
The cost center extrinsic nameCostCenterThe header Extrinsic name your platform carries the cost center in
The cost center segment nameCostCenterThe AccountingSegment type your order lines carry the cost center in

The last two are configuration on our side, not constants: Ariba and Coupa do not agree on where the cost center goes or what it is called, and one customer may run both platforms at once. Tell your SpendOne counterpart which one your platform emits, and under which name, and they set it on the connection.

The endpoint​

POST https://api.spendone.tech/punchout/v1/{tenant_id}/cxml
Content-Type: text/xml; charset=utf-8
MethodPOST only
TransportHTTPS. There is no HTTP listener
BodyOne cXML document, UTF-8
Body ceiling5 MB. A larger body is rejected before parsing
Content typeNot enforced. Send text/xml; charset=utf-8 or application/xml
CompressionNot supported. Send the document uncompressed
Rate limitBurst of 20 deliveries per source IP per tenant, then one every 30 seconds
AuthenticationThe shared secret inside the document. No HTTP header, no mTLS, no OAuth

The rate limit is sized far above a real integration, which delivers a handful of orders an hour. A throttled delivery is answered with the same 401 an authentication failure gets, so retry with backoff rather than treating it as a credential problem.

The endpoint is deliberately absent from the API reference: it is served outside /api/v1, it carries no bearer token, and it answers cXML rather than the JSON error format the rest of the API uses. This page is its documentation.

Authentication​

The credential travels inside the document, in Header/Sender/Credential/SharedSecret:

<Sender>
<Credential domain="NetworkID">
<Identity>AN01000000123</Identity>
<SharedSecret>so_px_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</SharedSecret>
</Credential>
<UserAgent>Ariba Buyer 9r1</UserAgent>
</Sender>

Three things are checked, and all three have to hold:

  1. The shared secret resolves to a live credential. SpendOne generates it (every SpendOne punchout secret starts with so_px_), shows it to your SpendOne counterpart exactly once at creation, and stores only a hash. If it is lost, a new one is minted and the old one revoked; it cannot be read back. The one exception is SAP Business Network, where the secret is the one on SpendOne's own supplier account and SpendOne enrolls it on your connection (see the SAP Business Network section).
  2. The tenant id in the path is a tenant the credential is enrolled on. One secret can be enrolled on several tenants, which is how one SAP Business Network secret serves every SpendOne customer, but a delivery to a tenant it is not enrolled on is refused exactly like an unknown secret.
  3. From/Credential/Identity matches the buyer identity configured on the connection. It is compared case-insensitively, with leading and trailing whitespace ignored.

:::warning The trailing-whitespace trap Paste the secret with no surrounding whitespace, and check what your platform stored. A secret copied out of a PDF, an email or a spreadsheet routinely carries a trailing newline or a non-breaking space, which is invisible in every configuration screen and authenticates as a completely different secret. This is the single most common failure in this integration family. If your first live delivery comes back 401 and everything looks right, re-enter the secret by hand before opening a ticket. :::

To/Credential/Identity is the identity you were given for SpendOne. Send it as agreed at onboarding, alongside its @domain.

The answer contract​

Every response is HTTP 200. The outcome is in the cXML Status element, never in the transport status code. A buyer platform that branches on the HTTP status will read every rejection as a success.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.071/cXML.dtd">
<cXML payloadID="01K6...@spendone.com" timestamp="2026-09-22T07:41:08Z">
<Response>
<Status code="200" text="OK"></Status>
</Response>
</cXML>

This is the whole vocabulary SpendOne can emit on this endpoint:

Status/@code@textBodyWhat it meansWhat to do
200OKemptyAccepted. The order is queued for conversion into a purchase requestNothing. Do not redeliver
200OKduplicateThis exact payloadID already arrived. Nothing was created twiceNothing. Your retry was safe
400Bad RequestemptyThe document could not be parsed or could not be accepted for this connectionFix the document or the configuration and send again. See below
401UnauthorizedemptyAuthentication failed, or the delivery was rate limitedCheck the secret, the tenant id, the From identity and the connection's state
450Not ImplementedemptyThe document is well formed but is not an OrderRequestSend an OrderRequest. No other document type is accepted here
500Internal Server ErroremptySpendOne could not handle the delivery at all: a backend failure, not a problem with your documentRetry with backoff. Raise it with SpendOne if it persists

401 is deliberately undifferentiated. A wrong secret, a revoked secret, an unknown tenant, a tenant that does not match the credential, an identity mismatch, an inactive connection and a rate-limited delivery all produce one byte-identical response, after a random delay. That is a security property, not a gap in the documentation: telling those cases apart would turn the endpoint into an enumeration oracle. Do not spend a day trying to distinguish "wrong secret" from "wrong tenant" from the wire. Ask your SpendOne counterpart, who can see which one it was.

Two classes of 400 exist, and they behave differently on our side:

  • Before authentication, a document that is not well formed XML, that is missing a required field, or that fails one of the validity rules below. Nothing is stored.
  • After authentication, an order the customer's own configuration will not take: the nominated contact resolves to nobody who can own an order, or the connection names a legal entity that can no longer be booked against. The document is stored and is visible to the customer's SpendOne administrator with the offending value, so they can fix it.

What makes a document malformed​

Any of these is answered 400, before any credential work:

  • Not well formed XML.
  • Missing payloadID on cXML, or missing orderID on OrderRequestHeader.
  • No ItemOut elements at all.
  • Total missing, empty, zero or negative.
  • A negative unit price, a quantity that is not a positive number, a lineNumber that is not a positive integer, or the same lineNumber on two lines.
  • A line currency that differs from the order currency, or a currency code that is not three ASCII letters.
  • A negative tax amount, a tax currency that differs from the order currency, or a TaxDetail with no category.
  • A number with more than six fractional digits or more than 15 significant digits.

What SpendOne reads out of your OrderRequest​

ElementHow it is used
cXML/@payloadIDWire-level idempotency key. A repeat is answered 200 OK / duplicate
OrderRequestHeader/@orderIDYour order number. It is the business key, and it is what appears on everything we send back
OrderRequestHeader/@typeWhat the document is: new (the default), update (a change order), or delete (a cancel order). See Idempotency, retries, change orders and cancel orders
OrderRequestHeader/@orderVersionYour revision counter for the document, carried onto the record when an order is held or canceled. Not compared: the order id and its content decide what a delivery revises
OrderRequestHeader/DocumentReference/@payloadIDThe delivery your change or cancel order supersedes, carried onto the record beside the version. Not a resolution key
OrderRequestHeader/@orderDateCarried onto the confirmation we send you
OrderRequestHeader/Total/MoneyThe payable. It funds the virtual card, whatever the tax treatment
OrderRequestHeader/Tax, TaxDetail@percentageRateThe VAT rate, and the net-versus-gross decision (below)
OrderRequestHeader/ContactWho the order belongs to. role="endUser" wins, then role="buyer"; the first non-empty Email is matched against the customer's SpendOne users
OrderRequestHeader/ExtrinsicThe cost center, under the extrinsic name configured on the connection. The value is matched case-insensitively and trimmed. Read first
ItemOut/Distribution/Accounting/AccountingSegmentThe cost center, when the header extrinsic names none of the owner's cost centers: the segment whose Name (or Segment/@type on the deprecated form) is the segment name configured on the connection, its @id being the cost center code. One cost center per order, so the first such segment in document order wins. This is the Ariba shape
OrderRequestHeader/ShipToRendered to a one-line delivery address. Read leniently, never a reason to reject
ItemOut/@quantity, @lineNumberOne purchase-request line per ItemOut. A missing lineNumber falls back to the element's position
ItemOut/ItemID/SupplierPartIDKept on the line, and compared when a revised order arrives
ItemOut/ItemDetail/UnitPrice/MoneyLine unit price. Line amount is unit price times quantity
ItemOut/ItemDetail/DescriptionThe line's name in SpendOne
ItemOut/ItemDetail/UnitOfMeasureCarried onto the relayed invoice
ItemOut/SupplierID, ItemDetail/ManufacturerNameThe merchant named on the virtual card. First SupplierID across the lines wins, then the first ManufacturerName
Header/From, Header/SenderAuthentication, as described above

Deliberately not read: BillTo (which legal entity the spend books to is connection configuration, not document data), Request@deploymentMode, Comments, Shipping, Classification, and every Distribution segment other than the configured cost center one. Sending them is harmless.

@type is validated, not ignored: a value outside the DTD's new | update | delete vocabulary (or one spelled differently, DELETE included) is answered 400 as a malformed document, because that attribute decides whether money moves and guessing new for an unreadable value would swallow exactly the corrections the attribute exists to carry.

Net or gross is derived from the arithmetic​

cXML carries no tax-inclusive flag on Total, so SpendOne decides from the figures, with a one-cent tolerance for your own per-line rounding:

  • Total equals the sum of the lines plus the stated tax: the line prices are net and Total is tax inclusive. This is the classic German B2B shape, and the one in the worked example below.
  • Total equals the sum of the lines: the prices already include the tax, or carry none.
  • Neither matches (shipping folded into Total, or inconsistent figures): the order is booked gross, at the document's own rate if it stated one.

A document that states no tax at all is accepted and booked at the default VAT rate configured on the connection, with the assumption recorded on the purchase request. If your orders never carry tax, agree that rate at onboarding.

Three things that stop an order, and one that does not​

SpendOne never bounces an order over metadata we would have had to guess at. A cost center we cannot match, a VAT rate we cannot read and a delivery address we cannot parse all fall back, because you already approved the order on your side and we already answered 200.

Three answers are yours or the customer's, and those do stop the order:

  1. The nominated contact does not resolve to a user who can own the order. The email must match a SpendOne user, that user must be active, and they must hold at least one cost center. Answered 400 at delivery.
  2. The connection names a legal entity that no longer exists or cannot be booked. Answered 400 at delivery. This is customer-side configuration; there is nothing to change in your document.
  3. The order names a supplier the customer has blocked. This one is answered 200, because the block is discovered during conversion, after the acknowledgement. The order does not become a purchase request, and the customer's administrator sees it in their punchout document list. The customer unblocks the supplier, and you resend.

Idempotency, retries, change orders and cancel orders​

Two separate keys protect you, and they cover different mistakes:

  • The same payloadID twice (a plain retry): answered 200 OK with body duplicate. Nothing is stored twice.
  • The same orderID twice under different payloadIDs (your platform minted a fresh envelope for the retry): SpendOne compares a content digest of the two documents. Identical content converges onto the purchase request already created, and the delivery is recorded against it. One order, one purchase request.

The digest covers currency, Total, the tax figures, the line count, and each line's part id, quantity and unit price. It ignores payloadID, the timestamp, the credentials, the contacts, the extrinsics and ShipTo, precisely so that an ordinary retry is not mistaken for a change.

What a document declares itself to be outranks what its content digests to. Ariba routes New, Changed and Cancelled orders as three separately configured methods, and all three keep the original orderID: Ariba versions the document, not the number.

  • type="new" (or no type at all) is an order. Identical content under the same orderID converges as a retry; differing content is held as an undeclared revision (below).
  • type="update" is a change order. When the orderID matches an order SpendOne already converted, it is never applied automatically and never swallowed as a retry: identical content or not, SpendOne records it, answers 200, and leaves the live purchase request untouched. (The one exception: an update whose orderID matches nothing already converted becomes a new order, because the buyer's latest stated content is the order and there is nothing to revise.) The record names it a change order with your orderVersion and the superseded payloadID, and the customer's SpendOne administrator resolves it: applying the revision means canceling and deleting the existing purchase request and reprocessing the held document by hand.
  • type="delete" is a cancel order. It does not create anything and it is not recorded as a retry: SpendOne cancels the purchase request the orderID resolves to. The order, the payment card behind it and the buyer's request all close, and a reject confirmation is sent to your endpoint if confirmations are enabled. Two special cases, both deliberate:
    • The orderID matches no order SpendOne converted (it was refused at delivery, or it never arrived): answered 200 and recorded. There is nothing the buyer could fix, so SpendOne does not pretend otherwise.
    • The order was already placed with the supplier (you canceled after ConfirmationRequest type="accept" was sent): the cancel is held for the customer's operator rather than executed, because a cancellation cannot undo a placed order and the payment card may already have been charged. Your platform sees its 200 either way; resolve the discrepancy with the customer out of band. Cancel before placing whenever you can.

:::tip Change orders are held, not applied If your platform sends change orders under the original orderID, coordinate with your SpendOne counterpart: applying the revision means canceling and deleting the existing purchase request and reprocessing the held document by hand. A new orderID is a new order and needs no intervention. Note that a correction to the delivery address alone is not visible to the digest and is treated as an ordinary retry on a type="new" delivery. :::

What SpendOne sends back to you​

Documents flow the other way over the same protocol, POSTed to an endpoint you provide. This half is optional and is enabled per connection.

DocumentWhenNotes
ConfirmationRequest, type="accept"When the order is actually placed with the supplierNot at receipt: the synchronous Status already acknowledged that
ConfirmationRequest, type="reject"When the order is canceled, or a non-delivery is resolvedoperation="update" when an accept already went out, naming it in a DocumentReference
InvoiceDetailRequestWhen the supplier's invoice is verified and handed over to accountingOne document per punchout order, carrying that order's lines. Optional, enabled separately

No ShipNoticeRequest is sent. There is no per-line acknowledgement either: a cXML ConfirmationRequest from SpendOne is always whole-order, because a SpendOne purchase order is accepted or not as a whole.

A rejection always carries a buyer-readable sentence in ConfirmationHeader/Comments, from a closed vocabulary rather than free text, so it is safe to display in your procurement UI:

ReasonSentence
supplier_unavailableThe supplier cannot fulfill this order.
item_discontinuedThe item has been discontinued.
price_changedThe price has changed since the order was placed.
lead_time_unacceptableThe lead time is not acceptable.
duplicate_orderThis order duplicates an existing order.
requested_by_buyerCanceled at the buyer's request.
supplier_blockedThe supplier is blocked by company policy.
(default)Order could not be fulfilled.

Every outbound document carries Request/@deploymentMode, set from the connection (test or production) rather than derived from which SpendOne environment sent it. That matters during onboarding, when a customer's production realm frequently points at a SpendOne test environment for a while.

What your receiving endpoint has to do​

  • Be HTTPS, on a publicly resolvable address. Private and link-local addresses are refused, at configuration time and again at dial time.
  • Answer a cXML Status. We read your cXML status in preference to your HTTP status wherever one parses.
  • Deduplicate on payloadID. A redelivery repeats the original payloadID and the original bytes on purpose, so your side can recognize it as the same document.

How SpendOne treats your answer:

Your answerSpendOne
cXML 2xxDone
cXML 4xx, or HTTP 4xxTerminal. No retry. A SpendOne operator has to redeliver it
HTTP 5xx, 429, 408, or a transport failureRetried with backoff. Retry-After is honored when it is under 15 minutes
HTTP 2xx with a body we cannot parseTerminal, same as a 4xx

A 401 from your side is terminal even though it is the most likely thing to be fixed within minutes. The credential is inside the document, so retrying through a rotation would turn every rotation into a burst of failures.

:::note Supplier identity on a relayed invoice On an InvoiceDetailRequest, cXML's From is the invoicing party, which is the real supplier, not SpendOne. Some buyer networks refuse a document whose invoicing party is not the vendor on the purchase order. That is a relationship or AP configuration question on your network, not a defect on either side. The usual answer is to run the connection with confirmations only until the relationship is established; the supplier's invoice continues to reach you the way it does today. :::

If your platform is SAP Business Network (Ariba)​

Everything above is generic cXML. On SAP Business Network the network sits between your procurement system and SpendOne, and that changes who configures what.

Your system sends the order to the network, addressed to SpendOne's supplier account. The network authenticates you and then POSTs the order to SpendOne itself, rewriting the credentials on the way: From stays your ANID, To is SpendOne's ANID, and Sender becomes the network (AN01000000001), carrying the shared secret of SpendOne's supplier account in place of yours. So on Ariba you never handle a SpendOne secret or endpoint URL. Where orders go, and how they authenticate, is configured on SpendOne's supplier account, by SpendOne.

What you do​

  1. Relate to SpendOne as a supplier in your network account, the way your realm onboards any supplier, under the SpendOne ANID your counterpart gives you.

  2. Hand your counterpart your ANID (the AN0100... value of your account) early: they need it to create the connection that expects you. It must be an ANID; the connection refuses anything else on an Ariba platform.

  3. Say where your orders carry the cost center. Ariba books accounting per line, so the cost center usually travels as an accounting segment on each ItemOut, and there is no header Extrinsic at all. Tell your counterpart the segment name your realm uses:

    <ItemOut quantity="2" lineNumber="1">
    ...
    <Distribution>
    <Accounting name="DistributionCharge">
    <AccountingSegment id="CC-4001">
    <Name xml:lang="en">CostCenter</Name>
    <Description xml:lang="en">Berlin depot</Description>
    </AccountingSegment>
    </Accounting>
    <Charge><Money currency="EUR">200.00</Money></Charge>
    </Distribution>
    </ItemOut>

    Here the segment name is CostCenter and the cost center is CC-4001. The name is matched exactly, the id case-insensitively.

  4. Agree the VAT rate your orders do not carry (below).

  5. Name both realms. Test and production are different accounts (below).

The VAT rate your orders do not carry​

Ariba purchase orders frequently state no Tax element at all. SpendOne accepts such an order and books it at the connection's default VAT rate (see Net or gross is derived from the arithmetic). What that rate should be depends on how your realm prices: a realm that prices net needs a rate that is not in the order total, and there is no value that is right for both. Agree the rate with your SpendOne counterpart at onboarding. They are asked to state it explicitly when the connection is created and cannot leave it at a default, precisely because the answer is yours. If your orders do carry tax, nothing of this applies and the figures in the document win.

Test and production are two different accounts​

Your SAP Business Network test account has its own ANID; nothing of its configuration carries over to production. The ANID your SpendOne connection expects is realm-specific, so a cutover from test to production is a second SpendOne connection or an edit of the existing one's identity, never just a URL change. Tell your counterpart both ANIDs, and expect to prove the integration once per realm.

What SpendOne configures on its supplier account​

None of these is yours to set. They are listed so that you can recognize them when a delivery fails, and so that you know what to ask your counterpart about.

  • An enterprise account. SAP Business Network excludes cXML and EDI integration from a Standard supplier account, so cXML order routing needs an enterprise one.
  • All three order types routed. The network routes new orders, change orders and cancellations as three separate routing methods. All three point at SpendOne, with a customer-specific route to your tenant's endpoint URL. With only the first routed, the first cancellation would never arrive and SpendOne would keep a purchase request you withdrew.
  • Shared-secret authentication on every routing method. With the certificate method the network authenticates by client TLS and sends no SharedSecret at all, which the SpendOne endpoint answers 401.
  • Both attachment settings off: Leave order attachments online and do not include them in the cXML order, and Include PDF document.
  • No PunchOut profile URL. SpendOne serves no catalog session. Left empty, the network falls back to the order-routing URL; filled in, it sends a ProfileRequest, which is answered 450, and caches that answer for 24 hours.
  • NetworkID identities on both sides, in both directions. A document naming you under DUNS or another domain the connection does not expect is answered 401.
  • The return path. Order confirmations, and supplier invoices if that is enabled on your connection, are posted into the network at https://service.ariba.com/service/transaction/cxml.asp under SpendOne's ANID, and reach you from there.

:::warning The attachment settings refuse every order With either attachment setting enabled, the network posts the order as a multipart/related message: the cXML document with the attachment, or a PDF copy of it, packaged alongside. SpendOne parses the request body as one XML document, so a multipart message fails to parse and the order is answered 400 before anything else about it is looked at. Every order, not only orders that carry attachments. From your side nothing looks wrong: your platform shows the order as transmitted, and it is refused all the same. If every order on an otherwise working setup comes back refused, this is the first thing to ask your counterpart about. :::

Two answers to questions buyers ask​

Every document SpendOne sends declares its cXML version. All documents SpendOne posts, the synchronous Status answer to your orders included, open with the XML declaration and a DOCTYPE naming cXML 1.2.071. The network reads the version of a document from that declaration, so a document without one gives it nothing to read. The same tolerance holds in the other direction: your documents may carry their own DOCTYPE declaration, and it is read as version metadata, never fetched.

The rate limit is not relaxed for authenticated connections. The endpoint's rate limit (a burst of 20, then one delivery every 30 seconds) is sized far above what the network posts for a live integration, so you will not meet it. It is deliberately not exempted per connection either: a throttled delivery is answered with the same 401 a wrong secret gets, the network cannot tell the two apart, it retries hourly and gives up after ten attempts, and the order ends Failed on the network. The burst sits far above anything the network sends, which is the deliberate answer: the ceiling is never met, and keeping every refusal indistinguishable keeps the endpoint from becoming an oracle (see the authentication section).

Onboarding sequence​

On SAP Business Network, steps 2 to 4 and 6 are replaced by what the SAP Business Network section above lists: SpendOne enrolls its network secret on the connection and configures the routing and the return path on its own supplier account, and your part is the supplier relationship, your ANIDs, the cost center segment and the VAT rate.

  1. The customer creates the connection in SpendOne, under Administration, Punchout. It names your platform, the identity pair, the legal entity the spend books to, where the cost center comes from and the default VAT rate. On an Ariba connection the rate must be stated explicitly and the identity must be your ANID; the connection API refuses anything else.
  2. The customer mints a credential on that connection. The secret is displayed once.
  3. The customer hands you the endpoint URL and the secret, over a channel that does not add whitespace. The endpoint URL is shown on the connection page, ready to copy.
  4. You configure SpendOne as a supplier in your procurement platform: the endpoint as the order routing destination, the secret as the shared secret, your identity in From, SpendOne's in To.
  5. You send one test order. Start in your test realm with a small, single-line order and a contact whose email exists in the customer's SpendOne directory. Expect 200 OK. The customer can see the received document in their punchout document list straight away.
  6. For the return direction, give the customer your receiving endpoint URL and a shared secret of your own. They configure it on the same connection and can trigger a test delivery from SpendOne, which reports your cXML Status back verbatim.
  7. Go live: switch the connection's deployment mode to production when both directions have been proven.

A worked OrderRequest​

Net-priced lines with the VAT stated on top, which is the most common European shape. The lines sum to 250.00, the stated tax is 47.50, and Total is the tax-inclusive 297.50.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.071/cXML.dtd">
<cXML payloadID="ord-net-1@buyer.example.com" timestamp="2026-09-20T09:00:00+02:00">
<Header>
<From><Credential domain="NetworkID"><Identity>AN01000000123</Identity></Credential></From>
<To><Credential domain="NetworkID"><Identity>SPENDONE-ACME</Identity></Credential></To>
<Sender><Credential domain="NetworkID"><Identity>AN01000000123</Identity>
<SharedSecret>so_px_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</SharedSecret></Credential>
<UserAgent>Ariba Buyer 9r1</UserAgent></Sender>
</Header>
<Request deploymentMode="test">
<OrderRequest>
<OrderRequestHeader orderID="PO-9002" orderDate="2026-09-20T09:00:00+02:00" type="new">
<Total><Money currency="EUR">297.50</Money></Total>
<ShipTo><Address isoCountryCode="DE"><Name xml:lang="en">Berlin Depot</Name>
<PostalAddress><Street>1 Alexanderplatz</Street><City>Berlin</City>
<PostalCode>10178</PostalCode>
<Country isoCountryCode="DE">Germany</Country></PostalAddress>
</Address></ShipTo>
<BillTo><Address isoCountryCode="DE"><Name xml:lang="en">Northgate GmbH</Name>
<PostalAddress><Street>1 Alexanderplatz</Street><City>Berlin</City>
<Country isoCountryCode="DE">Germany</Country></PostalAddress>
</Address></BillTo>
<Contact role="endUser"><Name xml:lang="en">Dana Weber</Name>
<Email>dana.weber@northgate.example</Email></Contact>
<Tax>
<Money currency="EUR">47.50</Money>
<Description xml:lang="de">Umsatzsteuer 19%</Description>
<TaxDetail category="vat" purpose="tax" percentageRate="19">
<TaxableAmount><Money currency="EUR">250.00</Money></TaxableAmount>
<TaxAmount><Money currency="EUR">47.50</Money></TaxAmount>
</TaxDetail>
</Tax>
<Extrinsic name="CostCenter">CC-4001</Extrinsic>
</OrderRequestHeader>
<ItemOut quantity="2" lineNumber="1">
<ItemID><SupplierPartID>MSC-1001</SupplierPartID></ItemID>
<ItemDetail>
<UnitPrice><Money currency="EUR">100.00</Money></UnitPrice>
<Description xml:lang="en">Kestrel A4 copier paper, 80gsm</Description>
<UnitOfMeasure>BX</UnitOfMeasure>
<Classification domain="UNSPSC">14111507</Classification>
</ItemDetail>
</ItemOut>
<ItemOut quantity="1" lineNumber="2">
<ItemID><SupplierPartID>MSC-2002</SupplierPartID></ItemID>
<ItemDetail>
<UnitPrice><Money currency="EUR">50.00</Money></UnitPrice>
<Description xml:lang="en">Vantor gel pen, black</Description>
<UnitOfMeasure>EA</UnitOfMeasure>
<Classification domain="UNSPSC">44121704</Classification>
</ItemDetail>
</ItemOut>
</OrderRequest>
</Request>
</cXML>

Sent with:

curl -i -X POST \
-H 'Content-Type: text/xml; charset=utf-8' \
--data-binary @order.xml \
https://api.spendone.tech/punchout/v1/tenant_acme/cxml

The answer, HTTP 200:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE cXML SYSTEM "http://xml.cxml.org/schemas/cXML/1.2.071/cXML.dtd">
<cXML payloadID="01K6ZPQ8F7N2R4V6X8Z0B2D4F6@spendone.com" timestamp="2026-09-20T07:00:02Z">
<Response>
<Status code="200" text="OK"></Status>
</Response>
</cXML>

Troubleshooting​

SymptomWhere to look
401 on the first live deliveryWhitespace around the secret, first. Then the tenant id in the path, then From/Credential/Identity against what the connection has, then whether the connection is still active
401 after months of working trafficThe credential was revoked or rotated, or the connection was deactivated. Ask for a fresh secret
401 in bursts under loadThe rate limit. Back off and retry; a throttled delivery looks like an authentication failure by design
450Something other than an OrderRequest, usually a PunchOutSetupRequest, or a Request element naming a document SpendOne does not read. SpendOne serves no catalog session
400 on every documentWork through the malformed list above. orderID, payloadID, a zero Total and a missing ItemOut are the frequent ones
400 on every document, on SAP Business Network, with everything configured rightThe attachment settings on SpendOne's supplier account. Leave order attachments online or Include PDF document makes the network post multipart/related instead of plain XML, and the order fails to parse before anything else about it is read. Ask your SpendOne counterpart to check both are off (see the SAP Business Network section)
400 on some documents onlyUsually the contact: the email on the order does not match an active SpendOne user who holds a cost center. Your SpendOne counterpart can see the address we could not resolve
200 OK, but no purchase request appearsA blocked supplier, or a change order under an existing orderID. Both are visible to the customer's SpendOne administrator with the reason
400 on change and cancel orders onlyThe @type value is outside the DTD's new | update | delete vocabulary, or two DocumentReference elements are present. SpendOne validates @type rather than guessing, because it decides whether money moves
Orders land on the wrong cost centerThe extrinsic name or the segment name configured on the connection does not match what your platform emits, or the value matched none of the owner's cost centers. Both names are matched exactly. SpendOne falls back rather than rejecting, so this is silent by design
Confirmations never arriveConfirmations are enabled per connection and are sent at order placement, not at receipt. Ask the customer to run a test delivery, which reports your endpoint's answer verbatim

Anything this page does not answer: tech@spendone.com.