Skip to content

Getting started

From nothing to a validated document. Seven steps, and the request is the third of them.

The request you are working towards

One endpoint, two form fields, one header. This is the whole of it, and the rest of this page is about getting a key that makes it work.

bash
curl https://edocumenttools.com/api/v1/validate \
  -H "Authorization: Bearer $EDOCUMENT_API_KEY" \
  -F "target=en16931-1.3.16-ubl-2.1" \
  -F "file=@invoice.xml"

target names the rule set the document is measured against and is always required. file is the document itself. The API decides XML or PDF from the bytes, not from the filename or the declared content type.

1. Create a developer account

An account holds your projects, your keys and your usage. Sign-up asks for an email address and a password, and records which version of the Developer and API Terms you accepted.

The API is in beta. If self-service sign-up is not open on this deployment, the Validation API page says so and the contact form reaches the person who runs it.

2. Confirm your email address

A link arrives by email and is valid once. Until the address is confirmed you can sign in, but you cannot create an API key — a credential issued to an unproven address is a credential with no owner.

3. Create a project

A project groups the keys and usage for one integration. Most people start with one and add a second when they want staging usage counted separately from production. Keys belong to a project; usage is reported per project and per day.

4. Create an API key

The dashboard shows the secret once, at the moment it is created. It is stored as a digest and cannot be recovered or shown again — if you lose it, create a replacement and revoke the old one. Put it in an environment variable or a secrets manager immediately.

bash
export EDOCUMENT_API_KEY=edt_live_...

You can restrict a key to specific targets when you create it, which is worth doing when an integration only ever validates one country's documents. Authentication covers key shape, restrictions and how to replace a key without an outage.

5. Choose a target

There is no automatic detection. en16931-1.3.16-ubl-2.1 is a good first choice for a generic EN 16931 UBL invoice: it runs XML Schema validation and then the European semantic rules. The target catalogue lists every id with its pinned rule-set version and what it does and does not check.

6. Send the document

Raw Node, with no client library. Nothing here needs an install.

javascript
import { readFile } from 'node:fs/promises';

const key = process.env.EDOCUMENT_API_KEY;
if (!key) throw new Error('EDOCUMENT_API_KEY is not set');

const bytes = await readFile('invoice.xml');

const form = new FormData();
form.set('target', 'en16931-1.3.16-ubl-2.1');
form.set('file', new File([bytes], 'invoice.xml', { type: 'application/xml' }));

const response = await fetch('https://edocumenttools.com/api/v1/validate', {
  method: 'POST',
  headers: { Authorization: `Bearer ${key}` },
  body: form,
});

// A 4xx or 5xx is about the request, never about the invoice.
if (!response.ok) {
  const problem = await response.json();
  throw new Error(`${problem.code} (request ${problem.requestId}): ${problem.title}`);
}

const report = await response.json();

if (report.result.valid) {
  console.log('Valid against', report.target.id, report.target.version);
} else {
  for (const finding of report.result.findings) {
    console.log(`${finding.severity} ${finding.ruleId ?? ''} ${finding.message}`);
  }
}

fetch, FormData, Blob and File are global from Node 20 onwards, which is what this example assumes.

7. Read the answer

This is the step people get wrong, so it is worth stating flatly: an invalid invoice is answered with 200 OK and a report explaining why. An HTTP error status means the request was wrong — a missing field, an unknown target, a bad key — never that the document was.

  • result.valid is true only when every requested layer ran and passed.
  • result.partial is true when a layer produced no verdict. A report can be not-valid and not-failed at the same time, and the layer statuses are what tell you which.
  • result.findings[].ruleId is the official rule identifier and is the stable thing to branch on. The message text is English prose and may be reworded.

Validating documents takes the report apart field by field.

Where to go next

The TypeScript SDK

A Node client, @edocumenttools/sdk, is built and tested against this API. It is a thin typed wrapper: it holds no validation logic, runs server-side only, and returns the same report the endpoint above returns.

It is not on npm yet. The package is prepared for beta publication and has not been released, so npm install @edocumenttools/sdk will not resolve today. Everything on this page works without it — the API is plain multipart over HTTPS.

What it will look like:

typescript
import { EDocumentTools } from '@edocumenttools/sdk';

const client = new EDocumentTools({
  apiKey: process.env.EDOCUMENT_API_KEY,
});

const result = await client.validate({
  target: 'en16931-1.3.16-ubl-2.1',
  file: './invoice.xml',
});

// PASSED, FAILED or PARTIAL. A failed document is a returned report,
// never a thrown error: the API ran the request exactly as asked.
console.log(result.status);

for (const finding of result.result.findings) {
  console.log(finding.severity, finding.ruleId, finding.message);
}
  • target stays mandatory. The SDK adds no detection, no latest alias and no try-several behaviour.
  • result.status is PASSED, FAILED or PARTIAL, derived from the layer table. A failed document is a returned report, not a thrown error.
  • Typed errors separate the two things that share HTTP 429 — a rate limit and a daily quota — by their machine code.
  • Nothing is retried automatically, because there is no idempotency key in v1 and a repeated validation spends another unit of the daily allowance.