Transform output for Coupa using custom XSLT
If the default output and data templates do not provide the output needed for your Coupa system, you can use custom XSLT to transform output data. Custom XSLT can be used to:
- Modify or enrich invoice data.
- Add or remove XML elements.
- Validate invoice content before export.
- Access purchase order information retrieved from Coupa.
- Return processing errors back to AP Essentials.
There are two ways to apply custom XSLT in the target system settings.
-
Through a file that has been uploaded to the Resource view.
-
By pasting the XSLT directly in the target system settings.
Tungsten AP Essentials supports XSLT version 1.0. Other versions are not supported.
Available XSLT parameters
Your custom XSLT is applied to the Coupa integration output (Coupa REST API XML). You can optionally include the XSLT parameters below.
| Parameter | Description |
|---|---|
| ApsXml |
Contains the original document output from Tungsten AP Essentials in ReadSoft Online XML 2.0 format. |
| Configuration | Contains target system configuration values. Sensitive information, such as connection settings, are not included. These settings are included:
|
| PurchaseOrders | Contains purchase order data retrieved from Coupa during processing. |
Example XSLT
The following example uses the original document output from Tungsten AP Essentials and target system settings to transform the final document output sent to Coupa.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<!-- Output formatted XML -->
<xsl:output method="xml" indent="yes"/>
<!-- Include the original XML output from AP Essentials -->
<xsl:param name="ApsXml"></xsl:param>
<!-- Include the Export settings from the target system -->
<xsl:param name="Configuration"></xsl:param>
<!-- Identity the template that copies all XML nodes -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Process the invoice header -->
<xsl:template match="invoice-header">
<xsl:copy>
<xsl:apply-templates/>
<!-- Specify the value of a field. If this field does not exist in Coupa, it is ignored by Coupa. -->
<example-header-field-1>Example text</example-header-field-1>
<!-- Specify the value of a field, depending on a value in the target system settings. -->
<example-header-field-2>
<xsl:choose>
<!-- Check the value of the default chart of accounts in the target system settings. -->
<!-- If the default chart of accounts is "COA1", specify the value of customfield01 as the value of example-header-field-2. -->
<!-- Otherwise, specify the value of customfield02 as the value of example-header-field-2. -->
<!-- The values of customfield01 and customfield01 are taken from the original XML output from AP Essentials. -->
<xsl:when test="$Configuration/SystemConfiguration/DefaultChartOfAccounts = 'COA1'">
<xsl:value-of select="$ApsXml/Batches/Batch/Documents/Document/HeaderFields/HeaderField[Type='customfield01']/Text"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$ApsXml/Batches/Batch/Documents/Document/HeaderFields/HeaderField[Type='customfield02']/Text"/>
</xsl:otherwise>
</xsl:choose>
</example-header-field-2>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
By default, the Coupa integration output (Coupa REST API XML) is used as input to the XSL transformation. For this example, the XSL looks like the example below.
<?xml version="1.0" encoding="utf-8"?>
<invoice-header>
<document-type>Invoice</document-type>
<is-credit-note>false</is-credit-note>
<invoice-date>20230314</invoice-date>
<invoice-number>0001</invoice-number>
<payment-order-number/>
<total-with-taxes d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance"/>
<gross-total d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance"/>
<shipping-amount d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance"/>
<currency>
<code>USD</code>
</currency>
<supplier>
<number>74</number>
</supplier>
<account-type>
<name>COA-910</name>
</account-type>
<invoice-lines>
<invoice-line>
<line-num>1</line-num>
<description>Summary</description>
<order-line-num d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<price>100.00</price>
<quantity d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<TaxRate d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<TaxAmount d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<total d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<type>InvoiceAmountLine</type>
<accounting-total d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance"/>
<account>
<code/>
<segment-1>C1</segment-1>
<segment-2>100</segment-2>
<account-type>
<name>COA-910</name>
</account-type>
</account>
<currency>
<code>USD</code>
</currency>
</invoice-line>
</invoice-lines>
<line-level-taxation>false</line-level-taxation>
<requester-lookup-name/>
</invoice-header>
The ApsXml parameter in the XSLT above imports original document output from Tungsten AP Essentials. In this example, the example uses the document output XML below. Note customfield01 and customfield02, whose values are used in the custom XSLT.
<?xml version="1.0" encoding="utf-8"?>
<Batches xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Batch>
<Documents>
<Document>
<Parties>
<Party>
<Type>supplier</Type>
<Name>Acme</Name>
<ExternalId>74</ExternalId>
</Party>
</Parties>
<HeaderFields>
<HeaderField>
<Type>invoicenumber</Type>
<Text>0001</Text>
</HeaderField>
<HeaderField>
<Type>creditinvoice</Type>
<Text>false</Text>
</HeaderField>
<HeaderField>
<Type>invoicedate</Type>
<Text>20230314</Text>
</HeaderField>
<HeaderField>
<Type>invoicetotalvatexcludedamount</Type>
<Text>100.00</Text>
</HeaderField>
<HeaderField>
<Type>invoicecurrency</Type>
<Text>USD</Text>
</HeaderField>
<HeaderField>
<Type>chartofaccount</Type>
<Text>COA-910</Text>
</HeaderField>
<HeaderField>
<Type>customfield01</Type>
<Text>custom field 01 example data</Text>
</HeaderField>
<HeaderField>
<Type>customfield02</Type>
<Text>custom field 02 example data</Text>
</HeaderField>
</HeaderFields>
</Document>
</Documents>
</Batch>
</Batches>
The Configuration parameter in the XSLT above uses the Export settings from the target system. For this example, the settings look like the following XML.
<SystemConfiguration>
<DefaultChartOfAccounts>COA1</DefaultChartOfAccounts>
<BillingCombinationTemplate>C1-100-3500-140000</BillingCombinationTemplate>
<HeaderInvoiceLineDescription>Summary</HeaderInvoiceLineDescription>
<DefaultTaxCategory/>
<CustomHeaderFieldMapping/>
<CustomLineItemFieldMapping/>
<AutomaticTaxLineDescription>Automatic tax line</AutomaticTaxLineDescription>
<EnforceLineLevelTaxation>false</EnforceLineLevelTaxation>
<EnableMissedTaxParameterCalculation>false</EnableMissedTaxParameterCalculation>
<UseRemitToCode>false</UseRemitToCode>
<RemitToCodeMapping/>
<UseDefaultRemitToCode>false</UseDefaultRemitToCode>
</SystemConfiguration>
Given the input above, the XSLT produces the output below.
<?xml version="1.0" encoding="utf-8"?>
<invoice-header>
<document-type>Invoice</document-type>
<is-credit-note>false</is-credit-note>
<invoice-date>20230314</invoice-date>
<invoice-number>XSLT_test</invoice-number>
<payment-order-number />
<total-with-taxes d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
<gross-total d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
<shipping-amount d2p1:nil="true" xmlns:d2p1="http://www.w3.org/2001/XMLSchema-instance" />
<currency>
<code>USD</code>
</currency>
<supplier>
<number>74</number>
</supplier>
<account-type>
<name>COA-910</name>
</account-type>
<invoice-lines>
<invoice-line>
<line-num>1</line-num>
<description>Summary</description>
<order-line-num d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<price>100.00</price>
<quantity d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<TaxRate d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<TaxAmount d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<total d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<type>InvoiceAmountLine</type>
<accounting-total d4p1:nil="true" xmlns:d4p1="http://www.w3.org/2001/XMLSchema-instance" />
<account>
<code />
<segment-1>C1</segment-1>
<segment-2>100</segment-2>
<account-type>
<name>COA-910</name>
</account-type>
</account>
<currency>
<code>USD</code>
</currency>
</invoice-line>
</invoice-lines>
<line-level-taxation>false</line-level-taxation>
<requester-lookup-name />
<example-header-field-1>Example text</example-header-field-1>
<example-header-field-2>custom field 01 example data</example-header-field-2>
</invoice-header>
Note the results of the output. The value of example-header-field-1 is directly specified in the XSLT, whereas the value of example-header-field-2 was determined by using values from the target system settings and the original document XML.
Access purchase order data
The PurchaseOrders parameter contains purchase orders referenced by the invoice header or invoice lines. To access purchase order data in your XSLT, add the following parameter declaration.
<xsl:param name="PurchaseOrders"/>
The purchase order XML structure is similar to the structure returned by the Coupa API.
Example purchase order XML
The XML returned by the PurchaseOrders parameter can look like the example below.
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfCoupaPurchaseOrder>
<!-- First purchase order -->
<CoupaPurchaseOrder>
<PoNumber>PO-10001</PoNumber>
<CreatedAt>2026-01-15T10:01:48+01:00</CreatedAt>
<Status>issued</Status>
<!-- Supplier information -->
<Supplier>
<number>74</number>
<name>Example Supplier Ltd</name>
</Supplier>
<!-- Purchase order lines -->
<OrderLines>
<!-- Line 1 -->
<CoupaPurchaseOrderLine>
<LineNum>1</LineNum>
<Description>Server hardware</Description>
<Quantity>2.0</Quantity>
<Price>1000.00</Price>
<Total>2000.00</Total>
<Status>created</Status>
<Type>OrderQuantityLine</Type>
</CoupaPurchaseOrderLine>
<!-- Line 2 -->
<CoupaPurchaseOrderLine>
<LineNum>2</LineNum>
<Description>Installation services</Description>
<Quantity>1.0</Quantity>
<Price>500.00</Price>
<Total>500.00</Total>
<Status>created</Status>
<Type>OrderQuantityLine</Type>
</CoupaPurchaseOrderLine>
</OrderLines>
</CoupaPurchaseOrder>
<!-- Second purchase order -->
<CoupaPurchaseOrder>
<PoNumber>PO-10002</PoNumber>
<Status>issued</Status>
<OrderLines>
<CoupaPurchaseOrderLine>
<LineNum>1</LineNum>
<Description>Consulting services</Description>
<Price>200.00</Price>
<Total>200.00</Total>
<Type>OrderAmountLine</Type>
</CoupaPurchaseOrderLine>
</OrderLines>
</CoupaPurchaseOrder>
</ArrayOfCoupaPurchaseOrder>
Return processing errors from XSLT
Your XSLT can return validation errors to AP Essentials by adding a processing-error element to the output XML.
If the connector detects a non-empty processing-error element after the XSLT transformation:
- The export to Coupa fails.
- The document is returned to AP Essentials.
- The document status is set to ERROR.
- The error message becomes visible to users in AP Essentials.
- The document is not sent to Coupa.
The connector prefixes the message automatically with the following text:
the custom XSL transformation failed with the following error:
For example, if your XSLT generates the following element:
<processing-error> The following orders contain more than one line: PO-10001</processing-error>
The error shown in AP Essentials becomes:
The custom XSL transformation failed with the following error: The following orders contain more than one line: PO-10001
Example XSLT
The following example:
- Copies the original Coupa XML.
- Accesses purchase order information using the PurchaseOrders parameter.
- Checks whether any referenced purchase orders contain more than one PO line.
- Returns a processing error if matching purchase orders are found.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
exclude-result-prefixes="msxsl">
<!-- Output formatted XML -->
<xsl:output method="xml" indent="yes"/>
<!-- AP Essentials document XML -->
<xsl:param name="ApsXml"/>
<!-- Target system configuration -->
<xsl:param name="Configuration"/>
<!-- Purchase orders retrieved from Coupa -->
<xsl:param name="PurchaseOrders"/>
<!-- Identity template that copies all XML nodes -->
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<!-- Process the invoice header -->
<xsl:template match="invoice-header">
<xsl:copy>
<!-- Copy existing invoice header content -->
<xsl:apply-templates/>
<!-- Select purchase orders containing more than one PO line -->
<xsl:variable
name="badPOs"
select="$PurchaseOrders/ArrayOfCoupaPurchaseOrder/CoupaPurchaseOrder[count(OrderLines/CoupaPurchaseOrderLine) > 1]"/>
<!-- Add processing-error if matching POs were found -->
<xsl:if test="count($badPOs) > 0">
<processing-error>
<xsl:text>The following orders contain more than one line: </xsl:text>
<!-- Output PO numbers separated by commas -->
<xsl:for-each select="$badPOs">
<xsl:value-of select="PoNumber"/>
<xsl:if test="position() != last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</processing-error>
</xsl:if>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Important considerations
- The text inside processing-error is visible to users in AP Essentials.
- Do not include confidential information in error messages.
- Purchase orders are retrieved automatically during processing. No additional API calls are required.
- The purchase order XML structure may change if additional fields are added in future releases.
- Purchase orders are retrieved based on PO numbers referenced by the invoice.
Try it yourself
To transform output to Coupa using XSLT:
- Create your custom XSLT.
- Navigate to the Resources view and select Add.
-
Use the dialog that appears to select the file you created. The file appears in the
Resources view after it uploads.
Resources that contain executable code must be approved by Tungsten Automation personnel before they can be used.
- Select the Coupa AP target system service and select Configuration.
- In the XSL transformation setting, select XSLT resource file.
- Specify the XSLT file you created in step 1 in the XSL transformation file box and save your settings.
-
Process a document through Tungsten AP Essentials and
ensure that your custom output is sent to Coupa properly.
Before you begin production, test your transformation.