# Getting started with the AXS API

Submit a standard UAE tax invoice with a machine credential, interpret the
response, and retrieve its current status. Run the commands in order in **one
Bash session**. Use an agreed test environment and test participants for this
exercise. The sample contains fictional trading details; replace all business
and tax data before using it for a real transaction.

## Service boundary and corrections

AXS accepts the UAE electronic-invoicing categories: tax/commercial invoices and
credit notes, including the two self-billed tax categories. Your company or ERP
decides tax treatment, the actual supply date and correction grounds. A validation
pass checks the submitted data; it does not prove the transaction facts. Contracts,
certified milestones, the general ledger and bank reconciliation stay in your ERP.
The continuous-supply one-year limit runs from actual supply, not a contract anniversary.

Issue B2C/simplified receipts through your ERP or point-of-sale system under the
VAT rules applicable outside electronic invoicing. Pro forma documents and
quotations belong to your sales workflow; creating one does not itself establish
a tax point. None of these is an AXS electronic-invoice category.

Old FTA administrative exceptions for invoice/credit-note content or issuance expire
when electronic invoicing becomes mandatory for the person (VATGEX1 §4.5;
VAT Executive Regulation Articles 59(16)/60(8)). There is no legacy-exception switch.
A genuine draft may be replaced; an admitted document remains preserved.

Document details expose `invalidatedBy`: derived workflow links to admitted credit
notes, with one explicit preceding-invoice reference, matching parties, billing
mode and currency, and the full positive gross amount. Partial credits are not
summed, and combined references are not allocated to an original. The indicator
does not change any processing state or assert delivery/FTA acceptance. Refresh
the original detail after a correction. More than 100 candidates are not assessed;
an empty list does not prove there are no corrections outside this matching rule.
The official PINT-AE CreditReason code list names VD **Volume Discount.**, not void.
VD alone never invalidates an invoice; even a legacy explicit reference must still
meet the full-gross comparison. Current rule `ibr-055-ae` prohibits a VD credit note
from having a preceding reference, so an unreferenced VD cannot be linked
automatically. Validation stays intact; the source overrides the earlier VD/void premise.

English suffices for the commercial view. FTA submission is subject to Tax Procedures
Law Article 5 and its Executive Regulation Article 5: the FTA may accept English
and may request an Arabic translation approved under UAE translation law, by its
specified deadline. The English/Arabic PDF view does not certify that translation;
your company remains responsible. Downloading a PDF is obtaining a view, not an
AXS issuance event. Sector-specific hosting/localisation duties must be agreed
separately in the customer contract and checked against the residency policy.

## Before you start

Ask your AXS contact for the following handover. A token alone is insufficient.

- An onboarded **tenant ID** and a participant registered uniquely to that tenant.
  For this standard invoice the issuing participant is the seller. For
  self-billing it is the buyer; `SELLER_NOT_REGISTERED` remains the error code,
  with `buyer.endpoint` identifying that case.
- An **ACTIVE machine client** with purpose `erp-ingest`, its client ID and its
  one-time secret, delivered through an approved secure channel. Store the
  secret in your secret manager. Do not put it in source control, tickets,
  screenshots, browser storage or logs.
- The exact **API base URL**, **token URL**, expected **issuer** and API
  **audience**, plus the tenant and permitted scope. These are environment
  parameters, not values to infer from another deployment.
- The test seller/buyer TINs and TRNs, and confirmation that the test receiver is
  appropriate. The `0235` endpoint value is the 10-digit TIN, not the 15-digit
  VAT TRN. The TRN must agree with that party's TIN.
- Bash, curl, jq and uuidgen. Download the
  [sample invoice](../../examples/ingest/valid-invoice.json) and keep its absolute
  filename. In a repository checkout this is `examples/ingest/valid-invoice.json`.

Creation of tenants and participant onboarding are operator responsibilities.
A tenant administrator manages machine clients in AXS eInvoice's API-client
screen — **Manage → API clients**, which the *Get credentials in AXS eInvoice*
button in this page's header opens directly when the portal address is
configured for this deployment. An ERP credential cannot create, rotate or
revoke other credentials.
If provisioning is unavailable, AXS must complete the handover before you start;
a stub-generated secret is not a usable Keycloak credential.

## Environment and authentication

Set these **non-secret** parameters from your handover before running the code:

| Variable | Value to supply |
| --- | --- |
| `API_BASE_URL` | Tenant API URL, including any deployment base path, without trailing `/` |
| `TOKEN_URL` | Exact token endpoint: `<issuer>/protocol/openid-connect/token`; preserve any `/auth` prefix |
| `CLIENT_ID` | Your confidential machine client ID |
| `TENANT_ID` | The tenant bound to that client |
| `SELLER_TIN`, `SELLER_TRN` | Your registered test issuer's TIN and TRN |
| `BUYER_TIN`, `BUYER_TRN` | Agreed test receiver's TIN and TRN |
| `SAMPLE_FILE` | Absolute local path to the downloaded sample JSON |

Use HTTPS on deployed environments. Loopback HTTP is only for local testing.
The API verifies the JWT's **RS256 signature, issuer, audience and expiry**.
The client's audience mapper must include the API audience supplied by AXS
(the API's `KEYCLOAK_CLIENT_ID`), which need not equal your machine `CLIENT_ID`.
Sending an `audience` parameter yourself does not configure that mapper.

A machine token has `axs_principal_kind: machine`, `azp: <CLIENT_ID>`, a single
`tenant: <TENANT_ID>` and the space-separated scope `axs:ingest`. The four
submission/status routes below **all require** that scope and tenant
entitlement. Requesting a scope cannot grant one the client was not assigned.
`x-tenant-id` selects the tenant; it never grants access to another tenant.
`GET /info` is a public issuer/realm diagnostic, not a credential service or a
complete audience/provisioning handover.

The following requests `client_credentials`. The secret is read without echo;
it is sent to curl through standard input, not as a process argument. Tokens
and headers are held in a private temporary directory. Keep shell tracing off.

```bash
set -euo pipefail
set +x
: "${API_BASE_URL:?}" "${TOKEN_URL:?}" "${CLIENT_ID:?}" "${TENANT_ID:?}"
: "${SELLER_TIN:?}" "${SELLER_TRN:?}" "${BUYER_TIN:?}" "${BUYER_TRN:?}"
: "${SAMPLE_FILE:?}"
umask 077
WORK_DIR="$(mktemp -d)"
trap 'rm -f "$WORK_DIR/token.json" "$WORK_DIR/headers"' EXIT
printf 'Private working directory: %s\n' "$WORK_DIR"
cp "$SAMPLE_FILE" "$WORK_DIR/template.json"
if [ -z "${CLIENT_SECRET:-}" ]; then
  read -r -s -p 'Machine client secret: ' CLIENT_SECRET
  printf '\n'
fi
printf '%s' "$CLIENT_SECRET" | curl --silent --show-error --fail-with-body \
  "$TOKEN_URL" -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode 'scope=axs:ingest' \
  --data-urlencode 'client_secret@-' > "$WORK_DIR/token.json"
unset CLIENT_SECRET
jq -er '.access_token | select(type == "string" and length > 0)' \
  "$WORK_DIR/token.json" | while IFS= read -r token; do
    printf 'Authorization: Bearer %s\nx-tenant-id: %s\n' "$token" "$TENANT_ID"
  done > "$WORK_DIR/headers"
curl --silent --show-error --fail-with-body "$API_BASE_URL/me" \
  -H @"$WORK_DIR/headers" > "$WORK_DIR/me.json"
jq -e --arg tenant "$TENANT_ID" \
  '.kind == "machine" and .tenantId == $tenant and (.scopes | index("axs:ingest") != null)' \
  "$WORK_DIR/me.json"
```

`/me` must return HTTP 200 and the assertion prints `true`. A token response
contains `access_token`, `token_type` and `expires_in`; do not print the token.
Cache it only for its lifetime, renew shortly before expiry, and use a fresh
client-credentials request when needed. This flow does not depend on a refresh
token. Rebuild the header file after renewal. On shell exit the credential files are
removed, while invoice and result files remain in the printed private directory.
Keep those files and IDs for retries/reconciliation, then remove the directory
when no longer needed. Do not rerun document preparation to retry an existing
transaction: reuse its saved JSON. Do not enable automatic POST retries with
a freshly generated ID.

## First synchronous invoice

Prepare one document, then keep its **ID, UUID and content unchanged for retries**.
A new transaction needs both a new invoice ID and UUID. The fixed identifiers
and July dates in the downloaded template are replaced below. All SBDH and
party endpoint values remain aligned.

```bash
DOC_UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
DOC_ID="AXS-FIRST-$DOC_UUID"
ISSUE_DATE="$(date -u +%F)"
jq --arg id "$DOC_ID" --arg uuid "$DOC_UUID" --arg day "$ISSUE_DATE" \
  --arg seller "$SELLER_TIN" --arg sellerTrn "$SELLER_TRN" \
  --arg buyer "$BUYER_TIN" --arg buyerTrn "$BUYER_TRN" \
  '.invoice.id=$id | .invoice.uuid=$uuid | .invoice.issueDate=$day |
   .invoice.paymentDueDate=$day |
   .sbdh.sender=("0235:"+$seller) | .sbdh.receiver=("0235:"+$buyer) |
   .invoice.seller.endpoint.value=$seller | .invoice.seller.taxRegistrationId=$sellerTrn |
   .invoice.buyer.endpoint.value=$buyer | .invoice.buyer.taxRegistrationId=$buyerTrn' \
  "$WORK_DIR/template.json" > "$WORK_DIR/invoice.json"
HTTP="$(curl --silent --show-error -o "$WORK_DIR/sync.json" -w '%{http_code}' \
  "$API_BASE_URL/ingest" -H @"$WORK_DIR/headers" \
  -H 'Content-Type: application/json' --data-binary @"$WORK_DIR/invoice.json")"
printf 'Sync HTTP %s\n' "$HTTP"
jq . "$WORK_DIR/sync.json"
case "$HTTP" in 200|201) ;; *) exit 1 ;; esac
INTEGRATION_ID="$(jq -er '.integrationId' "$WORK_DIR/sync.json")"
DOCUMENT_PATH="$(jq -rn --arg id "$INTEGRATION_ID" '$id|@uri')"
curl --silent --show-error --fail-with-body \
  "$API_BASE_URL/documents/$DOCUMENT_PATH" -H @"$WORK_DIR/headers" \
  > "$WORK_DIR/document.json"
jq . "$WORK_DIR/document.json"
```

A first admission returns **201**; an identical replay returns **200** with
`created: false`. Example response (IDs vary):

```json
{"integrationId":"AXS-FIRST-<uuid>","outcome":"partial","created":true,"routeClass":"normal","rulePackId":"draft-uae-route-class-2026-10@provisional"}
```

Admission validates the canonical envelope, structural and endpoint rules,
issuer registration, and the installed PINT-AE XSD/Schematron pack. It records
`TRANSPORT/PENDING` (a document to a predefined buyer address starts on
`C5_C2/PENDING` instead — see
[Predefined buyer addresses (report-only)](#predefined-buyer-addresses-report-only)). **201 is not a delivery receipt.** `outcome` describes the
currently recorded legs: `partial` while pending, `failed` if a leg failed,
`compliant` if every present leg succeeded. It can change later and does not
prove that every externally required receipt has arrived. `rulePackId` here is
the route-classification stamp, not the version of the PINT-AE validation pack.

The document response contains `integrationId`, `legs`, `routeClass`,
`rulePackId`, `direction`, `header`, `dispatch` and `artifact`. For example,
its initial `legs` array is:

```json
[{"leg":"TRANSPORT","state":"PENDING","rank":0,"reopenEpoch":0,"appliedReopenIds":[]}]
```

Read each leg and the dispatch/receipt fields to follow progress. `artifact.stored`
means that an artifact is stored, not that the receiver accepted it. The detail
response does **not** currently contain an aggregate `outcome` field.
For an unknown document ID, or a document owned by another tenant, this endpoint
returns **404** with the standard error envelope (`statusCode`, `message`, `requestId`), the
same contract as `/documents/{id}/invoice`, `/ubl` and `/pdf`.

## Asynchronous submission and polling

Use a second ID and UUID for this second transaction. The server returns **202**
after accepting the job. Validation occurs in the worker. A parseable but invalid
invoice can therefore receive 202. Malformed JSON and authentication errors can
still be rejected on the HTTP request path.

```bash
ASYNC_UUID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
jq --arg id "AXS-ASYNC-$ASYNC_UUID" --arg uuid "$ASYNC_UUID" \
  '.invoice.id=$id | .invoice.uuid=$uuid' "$WORK_DIR/invoice.json" \
  > "$WORK_DIR/async-invoice.json"
HTTP="$(curl --silent --show-error -o "$WORK_DIR/accepted.json" -w '%{http_code}' \
  "$API_BASE_URL/ingest/async" -H @"$WORK_DIR/headers" \
  -H 'Content-Type: application/json' --data-binary @"$WORK_DIR/async-invoice.json")"
printf 'Async HTTP %s\n' "$HTTP"
jq . "$WORK_DIR/accepted.json"
[ "$HTTP" = 202 ]
MESSAGE_ID="$(jq -er '.messageId' "$WORK_DIR/accepted.json")"
STATUS_PATH="$(jq -er '.statusUrl' "$WORK_DIR/accepted.json")"
[ "$STATUS_PATH" = "/ingest/$MESSAGE_ID" ]
for attempt in $(seq 1 30); do
  curl --silent --show-error --fail-with-body "$API_BASE_URL$STATUS_PATH" \
    -H @"$WORK_DIR/headers" > "$WORK_DIR/job.json"
  STATUS="$(jq -er '.status' "$WORK_DIR/job.json")"
  case "$STATUS" in
    done|invalid|failed) break ;;
    queued|processing) sleep 2 ;;
    *) printf 'Unexpected status: %s\n' "$STATUS"; exit 1 ;;
  esac
done
jq . "$WORK_DIR/job.json"
[ "$STATUS" = done ]
jq -e '.result.kind == "created" or .result.kind == "duplicate"' "$WORK_DIR/job.json"
ASYNC_PATH="$(jq -er '.integrationId | @uri' "$WORK_DIR/job.json")"
curl --silent --show-error --fail-with-body "$API_BASE_URL/documents/$ASYNC_PATH" \
  -H @"$WORK_DIR/headers" > "$WORK_DIR/async-document.json"
jq . "$WORK_DIR/async-document.json"
```

The loop is bounded to about one minute. A timeout means **unresolved**; retain
the `messageId` and continue polling later, with a fresh token if needed. Do not
create another invoice to hide a timeout. Unknown message IDs return 404 for the
selected tenant. Example completed job, with a shortened placeholder message ID:

```json
{"messageId":"<64 hex characters>","status":"done","integrationId":"AXS-ASYNC-<uuid>","result":{"kind":"created","outcome":"partial","routeClass":"normal","rulePackId":"draft-uae-route-class-2026-10@provisional"},"error":null}
```

| Job status | What to do |
| --- | --- |
| `queued`, `processing` | Keep polling within your retry budget. |
| `done`, `result.kind: created` or `duplicate` | Read `integrationId`, then the document status. This is an ingest result, not final compliance or delivery. |
| `done`, `result.kind: conflict` | Same business-ID conflict as synchronous 409. `result` is exactly `{"kind":"conflict"}`; do not count it as a successful new invoice. |
| `invalid` | Terminal validation rejection. `result.violations` has the synchronous 422 list; `integrationId` is null. Correct the input/registration before resubmitting. |
| `failed` | Infrastructure retries exhausted; `error` carries the reason. Give support the message ID; identical resubmission does not restart it (an operator retry does). |

## One-call file submission

If your ERP already exports UBL 2.1 or the portal CSV layout, `POST /ingest/file`
takes the whole file in one request: the same token, `axs:ingest` scope and
`x-tenant-id` header as `POST /ingest`, with the file as the raw body.

```text
POST /ingest/file
Authorization: Bearer <access token>
x-tenant-id: <tenant id>
Content-Type: application/xml    (one UBL invoice)  or  text/csv  (many invoices)

<the file bytes>
```

The file is malware-scanned first. Every invoice in it then goes through the
**same ingest path** as `POST /ingest` — envelope, structure, issuer
registration, UUID uniqueness and the full PINT-AE rule pack — in file order.
A CSV file uses the tenant company profile as the seller, exactly like the
portal upload; without a profile the whole file is rejected. The answer is
**200** with one result per invoice:

```json
{"summary":{"total":2,"created":1,"duplicate":0,"conflict":0,"invalid":1},"items":[{"index":0,"sourceRef":{"kind":"csv-rows","from":2,"to":2},"invoiceId":"INV-1","kind":"created","integrationId":"INV-1","outcome":"partial","routeClass":"normal","rulePackId":"draft-uae-route-class-2026-10@provisional","violations":[],"warnings":[],"report":{"rulePackId":"pint-ae-1.0.4","valid":true,"layers":[],"findings":[]}},{"index":1,"sourceRef":{"kind":"csv-rows","from":3,"to":3},"invoiceId":"INV-2","kind":"invalid","violations":[{"rule":"CSV_ROW","path":"issue_date","message":"<reason>"}],"warnings":[]}]}
```

| `items[].kind` | Meaning |
| --- | --- |
| `created` | Admitted now — the same as a synchronous 201. Follow it with `GET /documents/{integrationId}`. |
| `duplicate` | The identical invoice was already admitted (synchronous 200). |
| `conflict` | Same tenant + invoice ID, different content (synchronous 409). Investigate; never change the ID to bypass it. |
| `invalid` | Rejected, nothing stored. Read every `violations[]` entry. |

`report` is the PINT-AE validation report of that submission, including
Schematron **warnings on an accepted invoice**; it is absent when the invoice
was rejected before the rule pack ran. **Idempotency** uses the same key as
`POST /ingest` — tenant + invoice ID + content — so re-sending the identical
file after a timeout or 5xx is safe: invoices already admitted come back as
`duplicate`. There is no separate idempotency header. A CSV invoice's UUID is
derived from its cell values, so the same rows give the same UUID; changing a
value of an invoice that was already admitted is a `conflict`.

A **422** with `violations` means the file itself was refused and no invoice
was processed: `FILE_CONTENT_TYPE` (send `application/xml`, `text/xml` or
`text/csv`), `FILE_MALWARE`, `FILE_PARSE`, `FILE_ITEM_LIMIT` (too many invoices
in one file) or `SELLER_PROFILE_MISSING` (CSV without a company profile). A
**413** means the body exceeds the request size limit, a **503** that the malware
scanner gave no verdict; split or retry the file accordingly.

## Errors and retry decisions

| HTTP status | Meaning and action |
| --- | --- |
| 200 / 201 | Sync duplicate / new admission respectively. For GET, inspect the response content. |
| 202 | Async acceptance only; poll and inspect `status` **and** `result.kind`. |
| 400 | Malformed request or missing/invalid `x-tenant-id` (after authentication). |
| 401 | Missing, invalid or expired bearer; also wrong signature, issuer or audience. Renew once with correct configuration. |
| 403 | Token is valid but tenant entitlement or machine `axs:ingest` scope is missing. Fix the handover; retrying unchanged cannot grant access. |
| 409 | Same tenant + invoice ID, different canonical invoice. Investigate the prior submission; never silently overwrite or change the ID to bypass it. |
| 413 | Request body larger than the configured limit (for example a large `/ingest/file` upload). Split the file; resending it unchanged fails again. |
| 415 | Unsupported `Content-Type` for the route. Send JSON to `/ingest`, and `application/xml`, `text/xml` or `text/csv` to `/ingest/file`. |
| 422 | Envelope, structural, registration, UUID uniqueness or PINT-AE validation rejection. Read every `rule`, `path`, `message`. |
| 429 | Rate limited; honor `Retry-After` when supplied and back off. |
| 5xx / network timeout | Outcome may be unknown. Query known IDs and retry identical input with bounded exponential backoff and jitter. Escalate persistent failure. |

The **actual response shapes** differ. 401/403 and other thrown HTTP errors
use an envelope like this (request ID, time and path vary):

```json
{"statusCode":401,"error":"Unauthorized","message":"missing bearer token","requestId":"<request-id>","path":"/ingest","timestamp":"<ISO timestamp>"}
```

403 uses `error: "Forbidden"` and `message: "principal is not entitled to this tenant"`
or `"missing required scope: axs:ingest"`. An invalid bearer uses
`message: "invalid token"`. Keep `requestId` / `x-request-id` for support, without
sending your Authorization header or secret. The 409 and 422 bodies are instead:

```json
{"error":"document already ingested with different content","integrationId":"AXS-FIRST-<uuid>"}
```

```json
{"violations":[{"rule":"SELLER_NOT_REGISTERED","path":"seller.endpoint","message":"issuing participant 0235:1995999999 is not registered to the submitting tenant; the tenant is registered as 0235:1000000777","detail":{"issuerParticipantId":"0235:1995999999","registeredParticipantIds":["0235:1000000777"]}}]}
```

`SELLER_NOT_REGISTERED` keeps its code. When your tenant already owns registered
participants, just not the one the invoice names, the violation also carries a
`detail` object (`issuerParticipantId` and your `registeredParticipantIds`) and the
message names both — usually a seller TRN that does not match your registration.
A tenant with no participant at all gets the short message and no `detail`.

`path` from AXS's own rules is a dotted path **rooted at the canonical invoice,
without an `invoice.` prefix** — `seller.name`, `uuid`, `lines[0].itemName`,
`seller.endpoint`. Parts of the request envelope that are not the invoice keep
their own root, so an SBDH finding reads `sbdh.sender`. A finding that names no
single field carries an empty string. PINT-AE rule-pack findings are the
documented exception: they use a Schematron rule ID (for example `ibr-132-ae`)
and an XPath location. Fatal/error findings reject; warnings alone do not. Do
not build a parser that assumes every path is a dot-separated JSON field.

The same `violations` body answers a rejected **query parameter**, with the
parameter name as `path`: `GET /peppol/verify` returns 422
`{"violations":[{"rule":"INVALID_TRN","path":"trn","message":"trn must be exactly 15 digits"}]}`
when `trn` is missing, repeated or not exactly 15 digits, and never asks the Peppol
network.

Every `message` is informational **English** text for logs and developers — branch
and localise on the stable `rule` code, never on the wording. The master-data saves
(`POST`/`PUT /partners`, `POST`/`PUT /products`, `PUT /profile`) answer with the same
body, the body field as `path`, for example
`{"violations":[{"rule":"MANDATORY_FIELD","path":"name","message":"name is required"},{"rule":"INVALID_TRN","path":"trn","message":"trn must be exactly 15 digits"}]}`.

Exercise replay, conflict and rejection on the same test invoice:

```bash
for variant in replay conflict invalid; do
  case "$variant" in
    replay) cp "$WORK_DIR/invoice.json" "$WORK_DIR/check.json"; EXPECTED=200 ;;
    conflict) jq '.invoice.lines[0].itemName="Changed widget"' \
      "$WORK_DIR/invoice.json" > "$WORK_DIR/check.json"; EXPECTED=409 ;;
    invalid) jq '.invoice.id=""' "$WORK_DIR/invoice.json" \
      > "$WORK_DIR/check.json"; EXPECTED=422 ;;
  esac
  HTTP="$(curl --silent --show-error -o "$WORK_DIR/check-result.json" -w '%{http_code}' \
    "$API_BASE_URL/ingest" -H @"$WORK_DIR/headers" \
    -H 'Content-Type: application/json' --data-binary @"$WORK_DIR/check.json")"
  printf '%s: HTTP %s (expected %s)\n' "$variant" "$HTTP" "$EXPECTED"
  jq . "$WORK_DIR/check-result.json"
  [ "$HTTP" = "$EXPECTED" ]
done
```

Synchronous idempotency is keyed by **(tenant, invoice.id)** and hashes the
parsed canonical **invoice**, with recursively sorted object keys. Formatting
and object-key order do not create a new invoice; arrays and field values still
matter. SBDH is validated but is not part of that content hash. Validation runs
before duplicate detection, so a retry may reject if registration or rules have
changed. A UUID reused by a different invoice ID in the same tenant yields
**422 `UUID_NOT_UNIQUE`**, not 409. There is no caller-supplied `Idempotency-Key`
header in this implementation.

Your ERP numbers its own invoices: `invoice.id` is sent as you assign it. The
portal's invoice number series (ADR-0230) apply only to invoices a person issues
by hand in the portal; the ingest body's optional `numberSeries` field is refused
with **422 `NUMBER_SERIES_PORTAL_ONLY`** for machine clients, so omit it. A
series skips any number your ERP has already used in the tenant, so the two
numbering sources never produce the same `invoice.id`.

Async message IDs hash **(tenant, complete parsed request body)**, including
SBDH, with stable object-key order. Identical resubmission returns the same
message ID and never creates a second job or a second processing run. A 202
means the submission, including its body, is durably stored with the job: if
queue delivery failed, the platform re-queues a job that stays `queued`
automatically, within minutes. An identical resubmission of a `queued` job also
re-queues it immediately. A resubmission does not restart an `invalid` or
`failed` job. For `failed`, give support the message ID; operators can retry it.
A changed body produces a new job, but that job still meets the synchronous
business-ID/UUID rules. Retry budgets and recovery are not an exactly-once
delivery guarantee.

## Predefined buyer addresses (report-only)

When the buyer's electronic address (IBT-049) is one of the three predefined
addresses of the OpenPeppol PINT-AE BIS (§1.5.3 "Predefined endpoint"), the
document is **not sent over the Peppol network** — there is no C3 to receive
it. AXS only reports it to the FTA (C5):

| Buyer address | Use |
|---|---|
| `0235:9900000097` | deemed supply |
| `0235:9900000098` | the buyer is not yet in scope of e-invoicing |
| `0235:9900000099` | export to a buyer that is not on Peppol |

The address decides, not the route class: an export to a buyer that has a real
Peppol ID is sent as usual, and a document classified `export` but addressed
to `0235:9900000097` is still report-only. The route class (`routeClass`) is a
label and does not change.

What you see on such a document:

- the first leg is `C5_C2/PENDING`, not `TRANSPORT/PENDING`; there is no
  `TRANSPORT` leg and `dispatch` stays empty;
- `outcome` becomes `compliant` once the FTA report is `REPORTED`;
- `GET /documents?reportOnly=true` lists only documents addressed to one of the
  three predefined addresses (`reportOnly=false` or omitted: no filter).

The buyer's TRN on these addresses follows the PINT-AE rule `ibr-135-ae`, not
a blanket requirement:

| Buyer address | Buyer TRN (IBT-048) |
|---|---|
| `0235:9900000099` with the export flag | optional — a foreign buyer usually has no UAE TRN |
| `0235:9900000097`, `0235:9900000098` | a TRN **or** a buyer identifier (`buyer.identifier`, IBT-046) |

Without either, the document is refused with **422**
`BUYER_IDENTIFIER_REQUIRED` at `buyer.identifier` before admission. The legal
registration identifier (IBT-047) does not replace them. `buyer.identifier` is
emitted as `cac:PartyIdentification/cbc:ID` without a scheme and is copied into
the FTA report. On every other address a tax invoice or tax credit note still
requires the buyer TRN (`MANDATORY_FIELD`); a commercial document does not.
In a CSV file (`POST /ingest/file`) the same value goes in the optional
`buyer_identifier` column; without it and without `buyer_trn` the invoice is
`invalid` with `CSV_ROW` at `buyer_identifier`. The PDF view prints it in the
buyer block as **Buyer identifier**.

For `0235:9900000098`, the UAE Electronic Invoicing Guidelines (§10.2) also
require the supplier to give the buyer a conventional tax invoice (for
example a PDF). AXS does not deliver it for you: this is your obligation as
the issuer. You can download the document's PDF from `/documents/{id}/pdf`.

## Tax category guidance

**Version 1.6 · 2026-10-12 · category code list: PINT-AE 1.0.4 (ibr-139-ae) · review by our external tax adviser in progress — every row is provisional until it is complete.**

This is guidance, not a decision. The right category follows from the facts of each transaction under UAE VAT law; AXS does not classify transactions for you, and no row replaces advice on your own case. When a row sends you to a tax adviser — or whenever you are unsure — ask before you issue the invoice.

The table maps common business situations to the tax category (IBT-151) and
the transaction-type flags (BTAE-02, `transactionTypeCode`) your ERP sends. AXS
never derives a category from a flag, a party or an amount: the category on
each line is the one you send. The same table, in English, Arabic and
Hungarian, is the portal help entry behind the tax-category selector. When a
row changes, its version and date change with it. A document whose lines are
all E or O is a commercial invoice (480) or a commercial credit note (81),
never a tax invoice: the rule set rejects E/O-only lines on a tax invoice
(ibr-151-ae) and any other category on a commercial document (ibr-122-ae).

Sources: [G] = UAE Electronic Invoicing Guidelines V1.1 (June 2026), section number; tax adviser = the written answers of our external tax adviser, which are not public; DL8.46 and AE-GoodsType = the published code lists of the installed rule set.

| Situation | Category · flag | On the document | Conditions | Ask a tax adviser when | Source |
|---|---|---|---|---|---|
| Domestic sale of goods or services to a business | `S` · no flag | Each line at the standard rate (currently 5%) with its VAT amount. | The supply is taxable in the UAE and no zero rate or exemption applies to it. | the supply might be zero-rated or exempt — [G] 10.5 names exports and certain healthcare, education and real-estate supplies as zero-rated. | [G] 10.5; tax adviser |
| Export of goods to a customer outside the UAE | `Z` · Exports | The 0% rate; the delivery address is mandatory; a foreign buyer without a Peppol ID gets the fixed export address 0235:9900000099. Add a reason code or wording only where the official code list prescribes one. For a qualifying zero-rated export use Z and keep the supporting export evidence (Guidelines §10.4). The PDF retains the category code; the cited sources prescribe no fixed export sentence. AXS does not create or verify that evidence for you. | Only a qualifying export is zero-rated ([G] 10.5); the conditions, including the evidence of export, come from the VAT law. | you are unsure whether the export qualifies for the zero rate or which evidence to keep. | [G] 10.4 (8); [G] 10.5; tax adviser |
| Services to a customer outside the UAE | `Z` / `O` · Exports | Outside the scope (O) on a commercial invoice (480) when the place of supply is outside the UAE; the 0% rate (Z) on a tax invoice when the place of supply is in the UAE and the export conditions are met; otherwise the standard rate (S). On a tax invoice the Exports flag and its address rules apply as for goods. AXS does not accept the Exports flag on a commercial document ([G] 10.4 does not list the scenario for it), and the fixed export address requires that flag — whether an O-only export should carry the flag is an open question to our tax adviser. | Two steps. (1) Place of supply: VAT Decree-Law Article 29 places a service where the supplier resides, i.e. in the UAE; only the exceptions of Articles 30–31 move it abroad — a GCC-registered customer, services on goods or real estate abroad, catering, cultural, sporting or educational events abroad, transport starting abroad, and telecommunication or electronic services where they are used and enjoyed — and then the supply is O. (2) Place of supply in the UAE: Z only under VAT Executive Regulation Article 31 — the customer has no place of residence in an Implementing State and is outside the UAE when the service is performed (a presence in the UAE of less than 30 days that is not effectively connected with the supply counts as outside, Article 31(2); FTA VATP040 counts the days over a rolling 12-month period), the service is not connected to UAE real estate or movable property, the anti-avoidance test of ER 31(3) does not apply, or the service is actually performed outside the Implementing States (Article 31(1)(b)); otherwise S at 5% — and S whenever the facts for Z cannot be established (FTA VATP019). | always, before you choose between Z, O and S — in particular when the customer has a UAE presence or branch, the service relates to UAE real estate or movable property, ER 31(3) may apply, the customer is GCC-registered, or the customer has several establishments (FTA VATP019 names the most closely connected establishment). | [G] 10.4 (8); [G] 10.5; VAT DL Art. 29–31; VAT ER Art. 31; FTA VATP019; FTA VATP040 |
| Customer or supply in a free zone | no single category — see the conditions · Free zone | The beneficiary ID becomes mandatory: the party that ultimately uses, consumes or owns what is supplied — usually the buyer itself. | The flag records the free-zone fact; it does not decide the VAT treatment, and AXS does not derive a category from it or from the beneficiary. | the supply involves a designated zone — its VAT treatment depends on the facts and the designated-zone rules. | [G] 10.4 (1); tax adviser |
| Deemed supply: own use, gifts, supplies free of charge | no single category — see the conditions · Deemed supply | A tax invoice (380) is mandatory — VAT Decree-Law Article 65(2): deliver it to the recipient if there is one, otherwise keep it in your records. The buyer address is fixed to 0235:9900000097; the invoice is not exchanged, only reported to the FTA. Not available on commercial invoices or on self-billing. | A deemed supply is a taxable supply (Decree-Law Articles 2(1) and 3): the line carries S at the rate of the underlying supply; E is impossible (Article 12(2): an exempt supply is never deemed); Z only where the underlying supply itself is zero-rated — to be confirmed by our tax adviser. The value is the cost of the goods or services (Article 37) and the market value between related parties (Article 36). [G] 10.4 gives as examples supplies for no consideration, gifts above the prescribed threshold, private use of business assets, and goods held when the VAT registration is cancelled; the Decree-Law also lists exceptions. | always: whether the transaction is a deemed supply at all, how to value it, and whether its underlying supply is standard-rated or zero-rated. | [G] 10.4 (2); VAT DL Art. 2(1), 3, 12(2), 36, 37, 65(2) |
| Selling reverse-charge goods to a VAT registrant in the UAE | `AE` · no flag | No VAT on the line; the GTIN and the type-of-goods code (BTAE-09) are mandatory, and the invoice must state why the reverse charge applies ([G] 10.5.1). The PDF states that the Recipient is required to account for VAT pursuant to Article 48 of Federal Decree-Law No. 8 of 2017 on Value Added Tax, as amended (VAT ER 59(1)(l)), with the paragraph the code carries: DL8.48.3.1–3 (oil, gas, hydrocarbons) cite Article 48(3); DL8.48.8.1–2 (gold and diamonds, electronic devices) cite Article 48(8) and add Cabinet Decision 91/2023 for electronics or 127/2024 for precious metals and stones; mixed or unknown codes keep Article 48 without a paragraph. The Arabic wording is our translation, pending review. | Both parties are VAT registrants, and the goods are among those [G] 10.5.1 lists: electronic devices, precious metals and precious stones, crude or refined oil, natural gas, pure hydrocarbons, metal scrap. Decree-Law Article 48(4) and Cabinet Decisions 91/2023, 127/2024 and 153/2025 require, before the date of supply, two written declarations from the buyer — the intended use (resale or processing) and its VAT registration — and the supplier to verify the buyer’s TRN and keep the declarations; the mechanism does not apply to a zero-rated supply. The code name “Gold and Diamonds” (DL8.48.8.1) is the vocabulary of the superseded Cabinet Decision 25/2018; silver, platinum and precious stones under Decision 127/2024 also use that code. Metal scrap (Decision 153/2025) has no code in the installed list, so an AE e-invoice for it cannot be issued. | the goods are metal scrap — the type-of-goods list of the installed rule set (AE-GoodsType) has no code for it — or you are unsure whether the goods, the buyer or the declarations fall under the reverse charge. | [G] 10.5; [G] 10.5.1; VAT DL Art. 48(4); VAT ER 59(1)(l); CD 91/2023, 127/2024, 153/2025; PINT-AE AE-GoodsType |
| Buying reverse-charge goods from a UAE supplier | `AE` · no flag | The supplier issues the e-invoice with the AE category and no VAT; you receive it, and as the buyer you account for the VAT. | The same goods and the same registrant conditions as on the selling side. | you are unsure how to report the VAT you account for — that is your VAT return, outside AXS. | [G] 10.5.1; tax adviser |
| Importing goods or services from abroad (reverse charge) | no single category — see the conditions · no flag | No electronic invoice from you: the import of concerned goods and services is not subject to e-invoicing ([G] 10.5.1). | This is the import reverse charge, not the domestic one — the AE category is not used for it. | you are unsure how to account for the import VAT — that is your VAT return, outside AXS. | [G] 10.5; [G] 10.5.1 |
| Resale of second-hand goods, antiques or collectors’ items under the margin scheme | `N` · Margin scheme | The structured invoice carries zero VAT as required by ibr-108-ae; this is not a VAT exemption. Under VAT ER 29(7), the PDF states “VAT charged on the profit margin” and omits the separate tax amounts — line VAT, the VAT breakdown and the VAT total, including its AED mirror on a foreign-currency document; the net, the gross total payable and its AED mirror stay (ER 59(1)(j)). The Arabic statement is our translation, pending review. | VAT ER 29(2) opens the scheme to second-hand goods, antiques over 50 years old and collectors’ items acquired without recoverable input tax; VAT is calculated only on the margin between purchase and resale price. [G] 10.4 gives a used car sold by a dealer and artwork bought from private collectors as examples. | you are unsure whether the goods, or the way you acquired them, qualify for the scheme. | [G] 10.4 (3); [G] 10.5; VAT ER 29(2); VAT ER 29(7); VAT ER 59(1)(j); FTA VATGPM1; ibr-108-ae |
| Financial services | `E` · no flag | Category E with the exemption reason code DL8.46.1; no VAT. | Financial services exempt under Article 42 of the VAT Executive Regulation are excluded from e-invoicing ([G] 7.3), and so are such services to non-residents that qualify as zero-rated exports; financial services that are standard-rated to residents are not excluded. | always: first whether the service needs an e-invoice at all, then its category. | [G] 7.3; [G] 10.5; DL8.46 |
| Residential property (lease or sale) and bare land | `E` · no flag | Category E with DL8.46.2 (residential) or DL8.46.3 (bare land); no VAT. A tax invoice needs at least one line outside E and O; a wholly exempt transaction goes on a commercial invoice (480). | [G] 10.5 names certain real-estate services as exempt and certain real-estate supplies as zero-rated — the same property can fall on either side. | always for real estate: whether the supply is exempt or zero-rated depends on the property and the supply. | [G] 10.5; DL8.46; tax adviser |
| Local passenger transport | `E` · no flag | Category E with DL8.46.4; no VAT. | The exemption code names local passenger transport ([G] 10.5, DL8.46.4). | you are unsure whether the transport, or a service bundled with it, qualifies. | [G] 10.5; DL8.46 |
| Compensation, damages, deposits and similar payments | no single category — see the conditions · no flag | If the payment is outside the scope of VAT: category O. A document of O lines only is a commercial invoice (480), not a tax invoice. | FTA Public Clarification VATP001 (compensation-type payments) classifies them by the reason for the payment, whatever the parties call it: a payment that only compensates a loss — liquidated damages for a breach or an early termination, lost earnings, late payment interest on a contractual price, a true fine or penalty, compensation for goods damaged or lost — is not consideration for a supply and is outside the scope; a payment for something the recipient supplies, grants or gives up — the contractual price enforced in a dispute, a cancellation charge, a payment for the right to use property, taking title to the damaged goods, an administrative fee labelled as a penalty — is consideration and follows the supply it belongs to. An insurance payout is not among the VATP001 examples. [G] 10.5 describes out of scope only in general: the place of supply is outside the UAE, or the VAT Decree-Law excludes the transaction from being a supply. | always — whether a specific payment is compensation or consideration depends on the facts and the contract. | [G] 10.5; FTA VATP001; tax adviser |
| Advance payment received before the supply | no single category — see the conditions · no flag | A tax invoice for the advance when it is received, in the category of the supply. The final invoice covers only the remaining balance and references the advance invoice; the prepaid amount stays empty. | Where receiving the advance creates the date of supply for the amount received. | the contract has milestones or retentions — whether and when they create a date of supply depends on the contract. | [G] 17.1; tax adviser |
| Continuous or recurring supply: rent, subscription, instalments | no single category — see the conditions · Continuous supply | The Continuous supply flag; the category of the supply itself. No further field becomes mandatory. | FTA VATGRE1 §12.3: a completed/certified single supply under Article 25 is taxed on the full amount at completion (or earlier invoice/payment); on an Article 26 continuous supply, the retained part is taxed at the earliest of payment, its invoice or 12 months after certified completion. A milestone label alone is not a tax point. | retention is involved and the contractual classification is uncertain. Guidelines §17.2 permits the AED 90,000 + AED 10,000 pattern only as a conditional example; it does not automatically reduce the taxable amount. Clarify first: completed single supply or continuous/periodic supply; the contract’s payment and invoicing terms; when the retained part becomes due; whether an invoice already exists for it; whether a payment or another date-of-supply event has occurred (VAT Law Articles 25–26). See [Continuous supply](help:scenario-continuous-supply). | [G] 10.4 (5); [G] 17.2; FTA VATGRE1 §12.3; tax adviser |
| Summary invoice for several supplies over a period | no single category — see the conditions · Summary invoice | The invoicing period, and one line per supply with its description, taxable amount and tax treatment. A negative total is not allowed — use a credit note. A return goes on a credit note, not on a negative line; AXS warns on a negative line (SUMMARY_NETTED_RETURN). The Summary invoice flag is for tax invoices: the rule set rejects it on a commercial invoice (480) and on a commercial credit note (81) (ibr-157-ae, deliberate) — leave the flag off there and state the period in the invoicing period field (IBG-14). | Issue within 14 days from the end of the calendar month of supply (VAT Executive Regulation, Article 59(13)(b)). AXS uses the earliest stated supply-side month, or the period-end month if no earlier date is stated. The month-end day is excluded; if the last day is a weekend or an official Federal Government holiday, AXS moves it to the next Business Day (Tax Procedures Law, Articles 1 and 49). A continuous-supply flag alone does not activate the month-end rule. | you want to net a return inside the summary invoice — acceptable only where the current technical summary-invoice rules expressly permit it. | [G] 10.4 (4); VAT ER Art. 59(13)(b); TPL Art. 49; ibr-157-ae; tax adviser |

**Summary invoice with a negative line.** An accepted invoice can carry a
`warnings` list: advisory findings that never reject, store or send the
invoice differently. A summary invoice (`transactionTypeCode` position 4 is
`1`) with a line whose net amount (IBT-131) is negative gets one
`SUMMARY_NETTED_RETURN` warning per such line, in line order, with `path`
`lines[i].netAmount`. A return belongs on a credit note that references the
original invoice, not netted against unrelated supply lines. Credit notes do
not get this warning. A summary invoice whose total is negative is still
rejected with `NON_NEGATIVE_AMOUNT`; branch on the `rule` code, not on the
message.

**Summary invoice spanning calendar months.** A summary invoice whose stated
supply-side dates — `invoicingPeriod.startDate`, `invoicingPeriod.endDate`,
`taxPointDate`, `delivery.actualDeliveryDate`, `paymentReceiptDate` — fall in
more than one calendar month gets one `SUMMARY_PERIOD_SPANS_MONTHS` warning
with `path` `invoicingPeriod.endDate`; the message lists the months. The VAT
Executive Regulation allows one summary tax invoice only for the supplies of
one calendar month (Article 59(6), with the deadline in Article 59(13)(b)), so
supplies of another month belong on a separate summary invoice. The invoice
is still accepted and sent; the issuing-deadline basis is unchanged (it counts
from the end of the earliest stated supply month). Credit notes do not get this
warning; a malformed date is a field error instead. Line-level dates are not
modelled. It is the fourth and last producer in the fixed `warnings` order.

## Secret rotation and revocation

A **human tenant administrator**, using the existing API-client screen, creates,
rotates and revokes clients. Its underlying tenant APIs (`/api-clients`,
`/api-clients/:clientId/rotate`, `DELETE /api-clients/:clientId`) require a human
`tenant-admin` principal; they are not an ERP client-credentials workflow.
A pending creation must be resumed, not recreated blindly. Secrets appear only
once on successful create/rotate/resume responses; list and revoke never reveal
one. A revoked client cannot be rotated back into service.

For planned rotation, agree a cutover with the administrator, receive the fresh
secret securely, replace the secret-manager value and obtain a new token using
the token command above. Rotation regenerates the Keycloak client secret; do
not assume a dual-secret overlap window. Verify a fresh `/me` and submission
with the new credential. On revoke, Keycloak disables the client before AXS
records `REVOKED`, preventing new token issuance by that client.

**Already issued JWTs remain usable at the AXS API until their expiry**, provided
the signature/issuer/audience/claims still verify. The API does not introspect
tokens or read the client's ACTIVE/REVOKED database state on each request.
Secret rotation or client disable does not itself invalidate those JWTs. The
exact remaining exposure follows their `exp`, not the time of the administrative
operation. In a credential incident, tell AXS about both the secret and any
issued tokens; do not assume revocation is immediate access-token cancellation.

## API reference and boundaries

On your supplied **tenant API** base URL, open `/reference` for the existing
self-hosted Scalar reference, or `/docs` for Swagger. `/docs-json` serves the live
contract; the [downloadable OpenAPI contract](openapi.json) is versioned with
this guide. In Scalar, select the correct environment/server URL and enter your
bearer token and tenant header before trying a request. Generated local-server
examples are not a deployment URL. This portal never asks you to store a secret.

The contract covers more than the machine routes in this guide: some tenant routes
require a human session/role or another scope. **Operator APIs** (`/ops/**`) run
on a separate protected listener and are not part of the integrator handover.
Do not use an internal admin URL, operator credentials or a provisioning
service-account secret in ERP configuration.

Polling remains available. A human tenant administrator can configure the
optional HTTPS callback described below when the deployment enables it. A
self-service sandbox is not included. Documentation
hosting/access and additional languages/themes are tracked in AXS-96; the full
customer onboarding, external delivery receipts and usage-count rehearsal are
tracked in AXS-144. This guide does not claim those deployment or onboarding
checks have passed.


## Tenant HTTPS callbacks

A human `tenant-admin` configures **Manage → Callbacks**, or the matching
`GET /callbacks` and `PUT /callbacks` tenant API. These are human administration
routes; ERP client-credentials tokens cannot change the destination or secret.
Read the current `version` and send it as `expectedVersion` when saving. A 409
means another administrator changed the configuration: reload before deciding.
`available:false` means the deployment is not ready to deliver callbacks.

Use a receiver you control, with valid HTTPS on port 443 and public IPv4 DNS.
Credentials in the URL, query strings, fragments, IP literals, internal/local
addresses and redirects are rejected. Every DNS answer must be public; the
resolved address is pinned for each connection while TLS still verifies the
original hostname. The request deadline is five seconds after DNS resolution.

Creation or explicit signing-secret rotation returns `secret` **once**. Store
it securely on the receiver; configuration reads never reveal it. Every save
creates a new version and cancels pending deliveries from the old version.
Prepare the receiver before enabling or rotating, and use pull access to recover
cancelled documents. Historical events are never sent to a newly chosen target.

AXS POSTs JSON with `schemaVersion:1`, `id`, `tenantId`, `integrationId`, `type`
and `occurredAt`. An `inbound-document` event also contains `document`, the
canonical invoice JSON. Receipt events use the established notification types:
`outbound-delivered`, `outbound-failed`, `reporting-reported`, `reporting-failed`,
`fta-outage` and `inbound-rejected`. The envelope and document are immutable for
that event ID.

`inbound-rejected` means AXS received a supplier's invoice for your company,
rejected it (the sender got a negative business response) and will **not**
deliver it as an `inbound-document`. It carries no `document`: fetch the details
with `GET /documents/{integrationId}` if you need them. Events that happened
before this type existed are not sent retroactively.

**Forward compatibility.** New `type` values can be added within
`schemaVersion:1` without notice. A receiver must verify the signature, then
acknowledge an event with an unknown `type` with 2xx and ignore it. It must not
reject it or fail the batch. A change to the envelope's shape would come with a
new `schemaVersion`.

Validate the following headers **before processing the body**:

- `x-axs-event-id`: must match the JSON `id`; durably deduplicate by tenant and ID.
- `x-axs-timestamp`: Unix seconds; reject timestamps outside your agreed freshness
  window (the controlled receiver uses five minutes).
- `x-axs-signature`: `sha256=` followed by the lowercase hex HMAC-SHA256 of
  `timestamp + "." + exact request-body bytes`, using the one-time signing secret
  as a UTF-8 string. Compare signatures in constant time, then parse the JSON.

Return 2xx only after durably accepting the event; an already accepted duplicate
should also return 2xx without repeating the business action. Delivery is
**at least once**: a lost response can cause the same event to arrive again.
Only a received 2xx records `DELIVERED`; enqueue alone never does. Network errors,
408, 429 and 5xx retry with exponential backoff, at most six attempts per budget.
Other 3xx/4xx responses are permanent failures; AXS never follows a redirect.

`GET /callbacks/deliveries` and the portal show the latest 100 delivery results,
attempt counts and HTTP/error status. A human administrator can retry a `DEAD`
event with `POST /callbacks/deliveries/:id/retry` (URL-encode the ID). It preserves
the event ID/body and needs the same target configuration; pending, delivered
or cancelled events are not reopened by duplicate clicks. The receiver must
retain deduplication records across these operator retries. A 2xx proves HTTP
acceptance, not the receiver's subsequent accounting or ERP processing.

## Credit-note adjustment fields

The optional internal `adjustment.eventDate` is a YYYY-MM-DD correction event date.
Its ground is the existing `creditNoteReasonCode`: DL8.61.1.A cancellation, B changed
nature/tax treatment, C changed consideration, D return **and** consideration repaid,
E tax/treatment applied in error (VAT Law 61(1)(a)–(e)). VD is separate, not a sixth ground.
For D both conditions must have occurred. The issuer/adviser determines the event.
The five events are a classification, not automatic credit-note grounds: a credit note is for a
reduction (VAT Law 62(2)); if the event results in more tax, send a new tax invoice (380)
referencing the earlier one; a treatment change without a monetary difference triggers neither
automatically. AXS does not check Article 61(2)–(3) and never derives the document kind.

For applicable tax corrections VAT Law 62(2) allows 14 days, excluding the event day;
an endpoint on a weekend or an official Federal Government holiday moves to the next
Business Day under TPL Articles 1 and 49 (holiday list: FAHR announcements, version 2026.1,
not yet complete for 2026 — see the issuing-deadline help).
Missing event: `ISSUE_DEADLINE_NOT_ASSESSABLE`; late: `ISSUE_DEADLINE_EXCEEDED`.
Both are advisory `warnings`, never a lateness-driven 422 or deletion. On commercial
credit notes and VD the calculation is a platform advisory, not an automatic legal duty.
No supply/payment/original-invoice/issue date substitutes for the event.

The event is stored and included with the reason in the atomic admission audit;
it never enters UBL/Peppol. CSV uses `adjustment_event_date` with `credit_note_reason`.
OpenAPI documents the same optional object and rejects a second `adjustment.ground`.

## Supporting documents (attachments)

`invoice.attachments` carries the PINT AE additional supporting documents (IBG-24). Each
entry needs an `id` (IBT-122) and may carry a `description` (IBT-123), an embedded file and
an external location:

```json
{"id":"TS-2026-07","description":"Timesheet July 2026",
 "embedded":{"content":"<base64>","mimeCode":"application/pdf","filename":"timesheet-2026-07.pdf"}}
```

```json
{"id":"DN-4471","externalUri":"https://erp.example.com/delivery-notes/4471"}
```

| Rule | Refusal (422, at the field) |
| --- | --- |
| `id` not blank (`ibr-052`) | `ATTACHMENT_ID` at `attachments[i].id` |
| `mimeCode` is one of `application/pdf`, `image/png`, `image/jpeg`, `text/csv`, `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/xml`, `application/vnd.oasis.opendocument.spreadsheet` (`ibr-cl-24`, exact, lower case) | `ATTACHMENT_MIME` |
| `mimeCode` is not `image/png` or `image/jpeg` (AXS rule: no QR code or barcode on the electronic invoice, `[G]` 5.3) | `ATTACHMENT_IMAGE_REFUSED` at `attachments[i].embedded.mimeCode` |
| `filename` not blank (`ibr-075`) | `ATTACHMENT_FILENAME` |
| `content` is non-empty standard base64 | `ATTACHMENT_CONTENT` |
| the decoded files of one invoice total at most 512 KiB | `ATTACHMENT_SIZE` at the file that crosses it |
| at most 10 supporting documents | `ATTACHMENT_COUNT` at `attachments` |
| `externalUri` is an absolute `https://` or `http://` address, at most 2048 characters | `ATTACHMENT_URI` |
| `description` not blank when present | `ATTACHMENT_DESCRIPTION_BLANK` |

The PINT AE pack allows PNG and JPEG, but an issued invoice may not carry them: the UAE
rules do not allow a QR code or barcode on the electronic invoice, an image could carry
one, and its content cannot be checked. Send a scanned document as `application/pdf`.
The accepted types at issue are therefore `application/pdf`, `text/csv`,
`application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`, `application/xml`
and `application/vnd.oasis.opendocument.spreadsheet`. Documents you RECEIVE are not
affected: an image attachment from another provider is delivered to you unchanged.

The size and count limits and the URI shape are AXS rules (the PINT AE pack has none); the
512 KiB keeps the base64 body inside the 1 MiB request limit with room for the invoice.
Every embedded file is malware-scanned before admission. An infected file is refused with
**422 `ATTACHMENT_MALWARE`** at `attachments[i].embedded.content`, quarantined (its
fingerprint, never the bytes) and nothing is admitted. When the scan cannot run, the
answer is **503**: retry the identical request later. A document without an embedded file is
never scanned. The file travels unchanged to the buyer on the Peppol network and inside the
FTA tax data document. CSV upload has no attachment column.
