# CI and the command line

Source: https://xmlvalid.com/docs/ci

> Check XML in every pull request, in a Git hook or in any script, with the same engine as the validator.

Everything here calls the [xmlvalid API](https://xmlvalid.com/docs/api) with a key from [Dashboard → API keys](https://xmlvalid.com/dashboard/api). Each document is one API validation: 100 a month on Free, 10,000 on [Pro](https://xmlvalid.com/pricing). The documents are validated on our servers and deleted as soon as the check finishes.

## Command-line client

`@xmlvalid/cli` needs Node.js 20 or newer and has no dependencies.

```bash
npm install -g @xmlvalid/cli
export XMLVALID_API_KEY=xv_…

xmlvalid validate invoices/*.xml --schema ubl-2.1-invoice
xmlvalid validate "feeds/**/*.atom" --schema atom-1.0
xmlvalid validate order.xml --schema-file schemas/order.xsd
xmlvalid validate --url https://example.com/sitemap.xml --schema sitemap-0.9
```

It prints each document with its errors, as `file:line:column`:

```
✓ invoices/0412.xml  valid
✗ invoices/0413.xml  2 errors
    invoices/0413.xml:14:0  Element 'cbc:IssueDate': '2026-13-01' is not a valid value of the atomic type 'xs:date'.
    invoices/0413.xml:31:0  Element 'cac:TaxTotal': This element is not expected.

2 documents: 1 passed, 1 failed.
```

**`-s, --schema <name>`**

A catalog slug or a saved schema, see [choosing the schema](#schemas). `auto` gives each e-invoice the catalog profile it names (Peppol BIS, XRechnung, EN 16931, French, KSeF and other national formats), for a folder of mixed invoices.

**`--schema-file <path>`**

One XSD, DTD or RelaxNG file sent with each document.

**`--rules <path>`**

Your own Schematron (`.sch`) file, run after the schema check (and after a catalog profile's own rules); each finding shows its rule id, such as `[BR-CO-15]`.

**`--doctype`**

Validate against each document's own DOCTYPE.

**`--url <address>`**

A document at a public URL; repeat for several.

**`-f, --format`**

`text`, `json` for scripts, or `github` for annotations and a job summary.

**`--fail-on-warnings`**

Treat warnings as failures.

**`--report <file.html>`**

Also write an HTML report of every document, with links to each rule's page, to keep as a build artifact or send on. Open it and print it to save a PDF.

**`--share`**

Also make a public link to that report, open for 30 days, and print it at the end (Pro). With `--format github` the link goes into the job summary too. If the link can't be made, the run carries on without it.

**`-j, --concurrency <n>`**

Documents validated at once, 4 by default. Keep it within your plan's requests per minute.

Globs support `*`, `**`, `?` and `{a,b}`; quote them so the shell passes them through. `xmlvalid schemas` lists the schemas you can use and `xmlvalid usage` shows this month's count.

## GitHub Actions

Store the key as a repository secret named `XMLVALID_API_KEY`, then:

```yaml
name: XML
on: [push, pull_request]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: xmlvalid/validate-action@v1
        with:
          files: |
            feeds/**/*.xml
            sitemap.xml
          schema: atom-1.0
          api-key: ${{ secrets.XMLVALID_API_KEY }}
```

Failing lines are annotated in the pull request's *Files changed* view, and the run gets a summary table. The inputs are `files`, `urls`, `schema`, `schema-file`, `rules`, `doctype`, `fail-on-warnings`, `report` (a path for the HTML report, to keep with `actions/upload-artifact`), `share` (`true` for a report link in the summary and the `report-url` output, Pro) and `api-key`. To check different folders against different schemas, add one step per schema.

## GitLab CI

Add `XMLVALID_API_KEY` as a masked CI/CD variable:

```yaml
validate-xml:
  image: node:22-alpine
  script:
    - npx --yes @xmlvalid/cli@1 validate "invoices/**/*.xml" --schema ubl-2.1-invoice
```

## pre-commit

With [pre-commit](https://pre-commit.com), staged XML files are checked before each commit (the key comes from your environment):

```yaml
repos:
  - repo: https://github.com/xmlvalid/cli
    rev: v1.0.0
    hooks:
      - id: xmlvalid
        args: [--schema, atom-1.0]
        files: ^feeds/
```

## Other CI and scripts

Anywhere with Node.js, `npx --yes @xmlvalid/cli@1 validate …` works without installing anything: Jenkins, CircleCI, Bitbucket Pipelines, Azure Pipelines, a Makefile. Without Node.js, call the API with curl and check `result.valid`:

```bash
curl -sf https://xmlvalid.com/api/v1/validations \
  -H "Authorization: Bearer $XMLVALID_API_KEY" \
  -F document=@sitemap.xml -F schema=sitemap-0.9 -F wait=60 \
  | jq -e '.result.valid'
```

For your own client, the API is described as [OpenAPI 3.1](https://xmlvalid.com/docs/api/openapi.yaml), which most languages can generate a client from.

## Choosing the schema

**A catalog schema**

Pass its slug, shown on each page of the [schema catalog](https://xmlvalid.com/schemas): `ubl-2.1-invoice`, `atom-1.0`, `sitemap-0.9`…

**Your own schema**

A single file can go with each request (`--schema-file`). A schema that spans several files (imports, includes, a folder of common types) is easiest to [save once in the dashboard](https://xmlvalid.com/dashboard/schemas) and pass as `my-12`; it is then versioned there, not sent with every document.

**None**

Without a schema the documents are checked for well-formedness only.

## Limits and exit codes

Files are limited by your plan (10 MB on Free, 1 GB on Pro), as are requests per minute; the client waits and retries when it goes over the per-minute limit. The exit code tells the result:

**`0`**

Every document is valid (warnings allowed unless `--fail-on-warnings`).

**`1`**

At least one document is invalid or could not be read.

**`2`**

A usage problem: a missing key, an unknown or broken schema, the monthly quota used up.

**`3`**

The API could not be reached or failed.
