v0.15.0

GitHubnpmES
Colour theme

sunat-cli

Peru's tax authority exposes ten different surfaces: SOAP with signed XML, REST with OAuth, a ticket queue, a resumable upload, a legacy inbox, a JSON API, and a stateful HTTP form session. This wraps them in one supervised agent-facing binary.

See what it coversnpm install -g @crafter/sunat-cli
Surfaces
10
one binary
Tests
500+
green
Modes
2
headless and supervised
# Install once. Runs on Node.
$ npm install -g @crafter/sunat-cli

# Ask what is present, and what each gap needs
$ sunat-cli doctor
  ready · agent-browser · node · config

# Ask what a command accepts. Versioned, so pin it.
$ sunat-cli schema cpe-factura
  { "version": "1.0.0", "command": "cpe factura emit" }

# Preview returns the real shape, plus a hash
$ sunat-cli cpe factura preview --params @f.json
  { "dryRun": true, "validacion": { "ok": true } }

# Commit. UBL 2.1, XAdES, SOAP, end to end.
$ sunat-cli cpe factura emit --params @f.json --yes
  Aceptado · CDR 0000 · F001-1234

What it covers

9 surfaces

Electronic invoices

Factura, boleta, nota de crédito, nota de débito

UBL 2.1 documents, XAdES-BES signatures, SOAP straight to SUNAT. Verified end to end against the beta endpoint.

$ sunat-cli cpe factura emit \
    --params @factura.json --yes

Shipping notices

GRE remitente, modal 02

REST with JWT. The tracker is idempotent, so a retry after a timeout resolves the original submission instead of duplicating it.

$ sunat-cli cpe gre emit \
    --params @guia.json --yes

Sales and purchase ledgers

SIRE RVIE and RCE

Pull the proposal, poll the ticket, download the ZIP, push corrections back through a TUS 1.0.0 upload.

$ sunat-cli sire rvie propuesta \
    --periodo 202504 --yes

Daily summaries and voids

Resumen diario, comunicación de baja

Boletas under S/700 batch into one daily summary. Voids carry the same ticket-polling contract.

$ sunat-cli cpe resumen send \
    --fecha 2026-04-29 --yes

Independent worker filings

RHE and F616

For RHE, the browser obtains the SOL entry, HTTP reaches the draft, the legal confirmation stays supervised, and XML/PDF download is wired for validation on the next real issuance. F616 keeps its headless API read path.

$ sunat-cli rhe emit \
    --params '{"empresa":"Client","descripcion":"Service","monto":100}' --preview-only

Lookups

Padrón RUC, consulta CPE, tipo de cambio

OAuth2 client credentials against the public REST surface. The padrón syncs incrementally so a local lookup answers in under a millisecond.

$ sunat-cli api consulta \
    --tipo 01 --serie F001 --numero 123

Buzón SOL

Messages and notifications, metadata only

Lists without opening detail, preserves contradictory counts as evidence, and detects changes with a private local snapshot.

$ sunat-cli buzon list

Secrets in the OS keychain

Certificate passwords, clave SOL

A hidden prompt writes to the macOS or Linux keychain, which keeps the value out of shell history, environment variables, and the process table.

$ sunat-cli keychain set CPE_CERT_PASSWORD

Schema introspection

25+ versioned schemas

An agent asks the binary what a command accepts instead of guessing field names. The version is part of the response, so a caller can pin it.

$ sunat-cli schema cpe-factura

Endpoints first, browser at the boundary

The F616 declaration page looks like a form. It is a single-page app talking to a JSON API, and the form fields are the least reliable way to reach it.

RHE is different: Menu SOL mints an ephemeral entry and the backend returns HTML. The CLI uses HTTP for deduction, identity, and details, renders the draft again, reserves DOM automation for the legal confirmation, and wires XML/PDF through endpoints after issuance.

  1. 01

    Bootstrap

    Open SOL and obtain the entry or token required by the official surface.

  2. 02

    Direct HTTP

    Call the API or form session and validate the server's real response.

  3. 03

    Confirm

    For RHE, keep the legal action under human control, then validate and save artifacts only when SUNAT returns real XML/PDF bytes.

How far each surface goes

3 of 10 shipped and verified

Percentages are a judgement about how much of each surface is wrapped and callable by an agent. The Buzón SOL metadata reader was verified with an own production account. Tax submissions remain beta.

Coverage by SUNAT surface, with completeness and state
SurfaceWrapped%State
REST lookupsConsulta CPE, padrón, tipo de cambio
90shipped
RHE and F616Supervised RHE; XML/PDF wired, live pending
85partial
Buzón SOLMetadata, snapshot and changes
45partial
InvoicesFactura, boleta, NC, ND
85shipped
Daily summary and voidResumen diario, comunicación de baja
80shipped
SIRERVIE ventas and RCE compras
70partial
Shipping noticesModal 02 only, transportista pending
50partial
Drivers2 of 5: mock and sunat-direct
40partial
Void with intent tokenShaped, not built
30planned
Production submissionsNever run against live credentials
10untested

What is next

Issue numbers link to the tracker

Built for a caller that is not trusted

An agent will get a field name wrong, retry a call that already succeeded, and read past an error it should have stopped on. Each rule below exists because one of those failures is cheap to prevent and expensive to undo when the other end is a tax authority.

Payloads, not flag soup
A JSON payload survives being written by a model; twenty positional flags do not.
Every mutation previews first
For RHE, --dry-run validates locally and --preview-only reconciles SUNAT's real draft before issuance is enabled.
JSON when stdout is not a terminal
The human view and the machine view come from one code path, so they cannot drift.
Schemas at runtime
The binary answers what a command accepts, so an agent never invents a field name.
Input hardening
The agent is not a trusted operator. A hallucinated RUC fails validation before it reaches SUNAT.
One skill file per the agentskills.io spec
Discovery works the same way for every agent that reads the standard.