Browser-side JavaScript examples for creating, validating, and parsing UBL electronic invoices using the InvoiceXML API. Self-contained HTML files using vanilla JavaScript with native fetch and FormData. Compatible with modern browsers: Chrome, Firefox, Safari, Edge. No build step, no framework, no dependencies.
For background on UBL itself (what it is, the profiles, where it is used), see the main repository README.
API keys embedded directly in browser-side JavaScript are visible to every visitor through View Source or DevTools. Anyone can extract the key and use it against your InvoiceXML quota.
These examples are appropriate for:
- Local prototyping and testing
- Internal tools behind your company's authentication
- Single-user desktop apps wrapped in Electron or Tauri
For public-facing production websites, route requests through your own backend so the API key stays server-side. See the Node.js examples or PHP examples for server-side integration patterns.
Sign up and generate a key here:
→ https://www.invoicexml.com/account/authentication
Set apiKey in the examples to the raw key only, without the Bearer prefix. If your account page shows the full header value (e.g. Bearer ixml_a1b2c3...), copy only the part after Bearer . The code adds the prefix itself when building the Authorization header.
- Any modern browser:
- Chrome 95+
- Firefox 90+
- Safari 14+
- Edge 95+
- An InvoiceXML API key
- Optional: a local web server (
python -m http.server,npx serve, etc.) for testing. The examples also work by opening the.htmlfiles directly viafile://.
| File | Operation | API endpoint |
|---|---|---|
create.html |
Build a UBL 2.1 XML invoice | POST /v1/create/ubl |
validate.html |
Validate a UBL file against the EN 16931 and Peppol rules | POST /v1/validate/ubl |
extract-json.html |
Parse a UBL XML into JSON | POST /v1/extract/json |
ai-convert.html |
(Experimental) Convert a plain PDF to UBL with AI | POST /v1/transform/to/ubl |
render.html |
Render UBL XML into a human-readable PDF | POST /v1/render/ubl/to/pdf |
Each file is a complete, runnable HTML page. Open it in a browser, paste your API key into the <script> block, and click the button.
<button id="run">Generate invoice</button>
<script>
const payload = {
invoice: {
invoiceNumber: 'UBL-2026-001',
issueDate: '2026-05-18',
currency: 'EUR',
buyerReference: 'PO-2026-5571', // Peppol requires a buyer reference
seller: {
name: 'Acme GmbH', vatIdentifier: 'DE123456789',
electronicAddress: { identifier: 'DE123456789', schemeId: '9930' }, /* ... */
},
buyer: {
name: 'Globex SAS',
electronicAddress: { identifier: 'FR40303265045', schemeId: '9957' }, /* ... */
},
paymentDetails: { paymentAccountIdentifier: 'DE89370400440532013000' },
lines: [{ quantity: 10, priceDetails: { netPrice: 150.00 },
vatInformation: { rate: 19.00 }, item: { name: 'Senior consulting' } }],
},
options: { profile: 'peppol-bis-3' },
};
document.getElementById('run').addEventListener('click', async () => {
const response = await fetch('https://api.invoicexml.com/v1/create/ubl', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + apiKey,
},
body: JSON.stringify(payload),
});
const xml = await response.text();
const blob = new Blob([xml], { type: 'application/xml' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-ubl.xml';
a.click();
});
</script>The XML is downloaded directly to the user's machine via URL.createObjectURL and a triggered <a download>. options.profile decides the CustomizationID and the rules applied; Peppol BIS Billing 3.0 is the default.
Full example: create.html | API reference
<input type="file" id="file" accept=".xml,application/xml,text/xml">
<button id="run">Validate</button>
<pre id="output"></pre>
<script>
document.getElementById('run').addEventListener('click', async () => {
const file = document.getElementById('file').files[0];
const form = new FormData();
form.append('file', file);
const response = await fetch('https://api.invoicexml.com/v1/validate/ubl', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
document.getElementById('output').textContent = await response.text();
});
</script>The file is read directly from the <input type="file"> element. No FileReader or Blob conversion needed: FormData.append(file) accepts the File object directly.
Full example: validate.html | API reference
Useful for displaying invoice details in a web UI, populating form fields from an uploaded UBL file, or building drag-and-drop invoice review tools.
const form = new FormData();
form.append('file', fileInput.files[0]);
const response = await fetch('https://api.invoicexml.com/v1/extract/json', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
const invoice = await response.json();
// The invoice fields sit at the root of the response.
console.log(invoice.seller.name, invoice.totals.grandTotalAmount);Full example: extract-json.html | API reference
Experimental feature. Human verification required before any production use.
AI extraction can make subtle mistakes that automated validators may not catch: wrong tax category codes, transposed amounts, missing seller VAT identifiers, incorrect currency formatting. Always review the output PDF before sending it to a customer or tax authority. See the AI conversion notes in the main README.
Full example: ai-convert.html | API reference
UBL has no visual layer: the XML is the invoice, which is fine for machines and useless for the person in accounts payable who wants to read it. This endpoint renders the XML into a formatted PDF preview, auto-detecting whether the file is CII or UBL syntax. The PDF is for reading only; the XML file remains the authoritative invoice for compliance and tax purposes.
const form = new FormData();
form.append('file', fileInput.files[0]);
form.append('language', 'de'); // en, de, or fr
const response = await fetch('https://api.invoicexml.com/v1/render/ubl/to/pdf', {
method: 'POST',
headers: { Authorization: 'Bearer ' + apiKey },
body: form,
});
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-preview.pdf';
a.click();Full example: render.html | API reference
The patterns below assume the API key is held server-side and proxied through your backend. Replace /api/ubl/... with your own proxy endpoints.
function CreateUblButton() {
const [status, setStatus] = useState('');
async function handleClick() {
setStatus('Generating...');
const response = await fetch('/api/ubl/create', { method: 'POST' });
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-ubl.xml';
a.click();
setStatus('Done');
}
return <button onClick={handleClick}>Generate invoice</button>;
}<template>
<button @click="handleClick">Generate invoice</button>
<p>{{ status }}</p>
</template>
<script setup>
import { ref } from 'vue';
const status = ref('');
async function handleClick() {
status.value = 'Generating...';
const response = await fetch('/api/ubl/create', { method: 'POST' });
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-ubl.xml';
a.click();
status.value = 'Done';
}
</script><script>
let status = '';
async function handleClick() {
status = 'Generating...';
const response = await fetch('/api/ubl/create', { method: 'POST' });
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-ubl.xml';
a.click();
status = 'Done';
}
</script>
<button on:click={handleClick}>Generate invoice</button>
<p>{status}</p>Use HttpClient with responseType: 'blob':
this.http.post('/api/ubl/create', payload, { responseType: 'blob' })
.subscribe(blob => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'invoice-ubl.xml';
a.click();
});- CORS errors in the browser console: if the browser blocks the request, route through your own backend (proxy) instead of calling the API directly from the browser.
- API key visible in DevTools: this is expected when the key is embedded in client-side code. For anything beyond local prototyping, move the call to a server-side proxy.
HTTP 401 Unauthorized: API key missing or invalid. Generate one at invoicexml.com/account/authentication. A frequent cause: settingapiKeyto the wholeBearer xxxvalue, which sendsBearer Bearer xxx. Set the raw key only.HTTP 400 Bad Requeston Create: a required field is missing or malformed. Frequent causes:IssueDatenot in ISO format (YYYY-MM-DD),Currencynot in ISO 4217 (EUR,USD), country codes not in ISO 3166-1 alpha-2 (DE,FR).- File input always empty:
fileInput.files[0]isundefineduntil the user actually picks a file. Always check before reading. - Mixed content warnings: if your page is served over
https://, all API calls must also behttps://. The examples usehttps://api.invoicexml.comso this is fine, just do not change it tohttp://. file://protocol limitations: opening the HTML directly viafile://works for these examples but breaks some browser APIs in stricter modes. Use a local server (python -m http.server,npx serve) if you hit issues.