Invoices

Submit and manage ZATCA-compliant invoices: simplified (B2C), standard (B2B), credit notes, and debit notes. All create endpoints support the Idempotency-Key header to safely retry requests.

EGS unit: You can send egs_unit_id in every request (one key, multiple units), or omit it and use an API key that has a default EGS unit set in the dashboard. The unit must match your API key environment (sandbox key → sandbox unit, production key → production unit). See Flow & integration.

POST/v1/invoices/simplified

Submit simplified (B2C) invoice

Creates a simplified tax invoice (B2C). Reported to ZATCA asynchronously. Returns invoice_id, uuid, status, amounts, QR code, pdf_url, and report_deadline.

Requires API Key (Bearer)Supports Idempotency-Key header

Request body

CreateSimplifiedInvoiceDto
FieldTypeRequiredDescription
egs_unit_idstringNoActive EGS unit ID. Omit if your API key has a default EGS unit set.
invoice_numberstringYes
invoice_datestringYesYYYY-MM-DD
invoice_timestringYesHH:mm:ss
sellerSellerDtoYes
line_itemsLineItemDto[]Yes
currencystringNoDefault: SAR
Nested: SellerDto
FieldTypeRequiredDescription
namestringYesSeller legal name
name_arstringNoArabic name
vat_numberstringYesVAT registration number
addressAddressDtoYesSeller address
Nested: AddressDto
FieldTypeRequiredDescription
streetstringYes
citystringYes
postal_codestringNo
countrystringYes
Nested: LineItemDto
FieldTypeRequiredDescription
descriptionstringYesItem description
description_arstringNo
quantitynumberYesMin 0.0001
unit_pricenumberYesIn SAR
vat_category"S" | "Z" | "E" | "O"NoVAT category code

Code examples

const res = await fetch('https://api.esnadapi.com/v1/invoices/simplified', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${apiKey}`,
    'Idempotency-Key': 'unique-key-123',
  },
  body: JSON.stringify({
    egs_unit_id: egsUnitId,
    invoice_number: 'INV-001',
    invoice_date: '2025-01-15',
    invoice_time: '14:30:00',
    seller: {
      name: 'Acme Ltd',
      vat_number: '300012345600003',
      address: { street: 'King Fahd Rd', city: 'Riyadh', country: 'SA' },
    },
    line_items: [
      { description: 'Product A', quantity: 2, unit_price: 50.0 },
    ],
  }),
});
const invoice = await res.json();

Success response (200)

{
  "invoice_id": "uuid",
  "uuid": "invoice-uuid",
  "status": "reported",
  "subtotal": 100,
  "vat": 15,
  "total": 115,
  "invoice_date_hijri": "1446-07-15",
  "qr_code": "base64-encoded-qr",
  "reported_at": "2025-01-15T10:05:00.000Z",
  "report_deadline": "2025-01-16T10:00:00.000Z",
  "pdf_url": "/v1/invoices/uuid/pdf"
}

Error responses

  • 400
    {
      "error": {
        "code": "EGS_UNIT_REQUIRED",
        "message": "Provide egs_unit_id in the request body or set a default EGS unit for this API key in the dashboard.",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "EGS_UNIT_NOT_FOUND",
        "message": "EGS unit not found",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "EGS_UNIT_ENV_MISMATCH",
        "message": "This API key is for production. Use an EGS unit in production or switch key.",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "EGS_NOT_READY",
        "message": "EGS unit onboarding not complete",
        "retryable": false
      }
    }
POST/v1/invoices/standard

Submit standard (B2B) invoice

Creates a standard tax invoice (B2B). Cleared synchronously with ZATCA. Returns signed_xml_url and pdf_url so you can download the signed XML (ZATCA compliance) and a human-readable PDF.

Requires API Key (Bearer)Supports Idempotency-Key header

Request body

CreateStandardInvoiceDto
FieldTypeRequiredDescription
egs_unit_idstringNoOmit if API key has a default EGS unit
invoice_numberstringYes
invoice_datestringYes
invoice_timestringYes
sellerSellerDtoYes
buyerBuyerDtoYesRequired for B2B
line_itemsLineItemDto[]Yes
currencystringNo
Nested: SellerDto
FieldTypeRequiredDescription
namestringYesSeller legal name
name_arstringNoArabic name
vat_numberstringYesVAT registration number
addressAddressDtoYesSeller address
Nested: AddressDto
FieldTypeRequiredDescription
streetstringYes
citystringYes
postal_codestringNo
countrystringYes
Nested: BuyerDto
FieldTypeRequiredDescription
namestringYes
name_arstringNo
vat_numberstringYes
addressBuyerAddressDtoYes
Nested: BuyerAddressDto
FieldTypeRequiredDescription
streetstringYes
citystringYes
postal_codestringNo
countrystringYes
Nested: LineItemDto
FieldTypeRequiredDescription
descriptionstringYesItem description
description_arstringNo
quantitynumberYesMin 0.0001
unit_pricenumberYesIn SAR
vat_category"S" | "Z" | "E" | "O"NoVAT category code

Code examples

const res = await fetch('https://api.esnadapi.com/v1/invoices/standard', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${apiKey}`,
    'Idempotency-Key': 'unique-key-456',
  },
  body: JSON.stringify({
    egs_unit_id: egsUnitId,
    invoice_number: 'INV-002',
    invoice_date: '2025-01-15',
    invoice_time: '14:30:00',
    seller: {
      name: 'Acme Ltd',
      vat_number: '300012345600003',
      address: { street: 'King Fahd Rd', city: 'Riyadh', country: 'SA' },
    },
    buyer: {
      name: 'Client Co',
      vat_number: '300098765400003',
      address: { street: 'Olaya St', city: 'Riyadh', country: 'SA' },
    },
    line_items: [
      { description: 'Service B', quantity: 1, unit_price: 115.0 },
    ],
  }),
});
const invoice = await res.json();

Success response (200)

{
  "invoice_id": "uuid",
  "uuid": "invoice-uuid",
  "status": "cleared",
  "subtotal": 100,
  "vat": 15,
  "total": 115,
  "qr_code": null,
  "signed_xml_url": "/v1/invoices/uuid/xml",
  "pdf_url": "/v1/invoices/uuid/pdf",
  "cleared_at": "2025-01-15T10:05:00.000Z"
}

Error responses

  • 400
    {
      "error": {
        "code": "EGS_UNIT_NOT_FOUND",
        "message": "EGS unit not found",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "EGS_NOT_READY",
        "message": "EGS unit onboarding not complete",
        "retryable": false
      }
    }
POST/v1/invoices/credit-note

Submit credit note

Creates a credit note referencing an original invoice. Required: original_invoice_uuid, reason, seller, line_items. If the original was B2B, buyer is required. Credit note total must not exceed original invoice total.

Requires API Key (Bearer)Supports Idempotency-Key header

Request body

CreateCreditNoteDto
FieldTypeRequiredDescription
egs_unit_idstringNoOmit if API key has a default EGS unit
invoice_numberstringYes
invoice_datestringYes
invoice_timestringYes
original_invoice_uuidstringYesUUID of the original invoice
reasonstringYes
reason_arstringNo
sellerSellerDtoYes
buyerBuyerDtoNoRequired if original was B2B
line_itemsLineItemDto[]Yes
currencystringNo
Nested: SellerDto
FieldTypeRequiredDescription
namestringYesSeller legal name
name_arstringNoArabic name
vat_numberstringYesVAT registration number
addressAddressDtoYesSeller address
Nested: AddressDto
FieldTypeRequiredDescription
streetstringYes
citystringYes
postal_codestringNo
countrystringYes
Nested: BuyerDto
FieldTypeRequiredDescription
namestringYes
name_arstringNo
vat_numberstringYes
addressBuyerAddressDtoYes
Nested: BuyerAddressDto
FieldTypeRequiredDescription
streetstringYes
citystringYes
postal_codestringNo
countrystringYes
Nested: LineItemDto
FieldTypeRequiredDescription
descriptionstringYesItem description
description_arstringNo
quantitynumberYesMin 0.0001
unit_pricenumberYesIn SAR
vat_category"S" | "Z" | "E" | "O"NoVAT category code

Code examples

await fetch('https://api.esnadapi.com/v1/invoices/credit-note', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${apiKey}`,
    'Idempotency-Key': 'credit-001',
  },
  body: JSON.stringify({
    egs_unit_id: egsUnitId,
    invoice_number: 'CN-001',
    invoice_date: '2025-01-20',
    invoice_time: '11:00:00',
    original_invoice_uuid: 'original-invoice-uuid',
    reason: 'Goods returned',
    seller: { name: 'Acme Ltd', vat_number: '...', address: {...} },
    line_items: [{ description: 'Refund Item A', quantity: 1, unit_price: 57.5 }],
  }),
});

Success response (200)

{
  "invoice_id": "uuid",
  "uuid": "credit-note-uuid",
  "status": "reported",
  "document_type": "credit_note",
  "subtotal": 50,
  "vat": 7.5,
  "total": 57.5,
  "reported_at": "2025-01-20T11:00:00.000Z",
  "cleared_at": null
}

Error responses

  • 404
    {
      "error": {
        "code": "ORIGINAL_INVOICE_NOT_FOUND",
        "message": "Original invoice not found",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "ORIGINAL_INVOICE_NOT_READY",
        "message": "Original invoice must be reported or cleared",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "BUYER_REQUIRED_FOR_B2B_NOTE",
        "message": "Buyer required when original invoice was B2B",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "CREDIT_NOTE_TOTAL_EXCEEDS_ORIGINAL",
        "message": "Credit note total must not exceed original invoice total",
        "retryable": false
      }
    }
POST/v1/invoices/debit-note

Submit debit note

Same request body as credit note. Creates a debit note referencing the original invoice. Same validation rules apply (original must be reported/cleared; buyer required for B2B; total must not exceed original).

Requires API Key (Bearer)Supports Idempotency-Key header

Request body

CreateDebitNoteDto
FieldTypeRequiredDescription
egs_unit_idstringNoOmit if API key has a default EGS unit
invoice_numberstringYes
invoice_datestringYes
invoice_timestringYes
original_invoice_uuidstringYes
reasonstringYes
reason_arstringNo
sellerSellerDtoYes
buyerBuyerDtoNo
line_itemsLineItemDto[]Yes
currencystringNo

Code examples

await fetch('https://api.esnadapi.com/v1/invoices/debit-note', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${apiKey}`,
    'Idempotency-Key': 'debit-001',
  },
  body: JSON.stringify({
    egs_unit_id: egsUnitId,
    invoice_number: 'DN-001',
    invoice_date: '2025-01-20',
    invoice_time: '12:00:00',
    original_invoice_uuid: 'original-invoice-uuid',
    reason: 'Additional charges',
    seller: { name: 'Acme Ltd', vat_number: '...', address: {...} },
    line_items: [{ description: 'Extra fee', quantity: 1, unit_price: 23.0 }],
  }),
});

Success response (200)

{
  "invoice_id": "uuid",
  "uuid": "debit-note-uuid",
  "status": "reported",
  "document_type": "debit_note",
  "subtotal": 20,
  "vat": 3,
  "total": 23,
  "reported_at": "2025-01-20T12:00:00.000Z",
  "cleared_at": null
}

Error responses

  • 404
    {
      "error": {
        "code": "ORIGINAL_INVOICE_NOT_FOUND",
        "message": "Original invoice not found",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "ORIGINAL_INVOICE_NOT_READY",
        "message": "Original invoice must be reported or cleared",
        "retryable": false
      }
    }
  • 400
    {
      "error": {
        "code": "BUYER_REQUIRED_FOR_B2B_NOTE",
        "message": "Buyer required when original invoice was B2B",
        "retryable": false
      }
    }
GET/v1/invoices

List invoices (cursor pagination)

Returns a paginated list of invoices. Filter by status, invoice_type, document_type, date range, and egs_unit_id.

Requires API Key (Bearer)

Query parameters

NameTypeRequiredDescription
cursorstringNoPagination cursor from previous response
limitnumberNoPage size
statusstringNoFilter by status
invoice_typestringNosimplified | standard
document_typestringNoinvoice | credit_note | debit_note
date_fromstringNoYYYY-MM-DD
date_tostringNoYYYY-MM-DD
egs_unit_idstringNoFilter by EGS unit

Code examples

const res = await fetch(
  `${BASE}/v1/invoices?limit=20&status=reported&egs_unit_id=${egsUnitId}`,
  { headers: { Authorization: `Bearer ${apiKey}` } }
);
const { data, pagination } = await res.json();

Success response (200)

{
  "data": [
    {
      "invoice_id": "uuid",
      "uuid": "invoice-uuid",
      "invoice_number": "INV-001",
      "invoice_type": "simplified",
      "document_type": "invoice",
      "status": "reported",
      "total": 115,
      "invoice_date": "2025-01-15",
      "created_at": "2025-01-15T10:00:00.000Z"
    }
  ],
  "pagination": {
    "limit": 20,
    "next_cursor": "cursor-token",
    "has_more": true,
    "total_count": 150
  }
}
GET/v1/invoices/:id

Invoice detail with timeline

Returns full invoice details including line items and status timeline (pending → submitted → reported → cleared).

Requires API Key (Bearer)

Code examples

const res = await fetch(`${BASE}/v1/invoices/${invoiceId}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const invoice = await res.json();

Success response (200)

{
  "invoice_id": "uuid",
  "uuid": "invoice-uuid",
  "invoice_number": "INV-001",
  "egs_unit_id": "egs-uuid",
  "invoice_type": "simplified",
  "document_type": "invoice",
  "status": "reported",
  "subtotal": 100,
  "vat": 15,
  "total": 115,
  "currency": "SAR",
  "invoice_date": "2025-01-15",
  "invoice_date_hijri": "1446-07-15",
  "reported_at": "2025-01-15T10:05:00.000Z",
  "cleared_at": null,
  "report_deadline": "2025-01-16T10:00:00.000Z",
  "signed_xml_url": "/v1/invoices/uuid/xml",
  "pdf_url": "/v1/invoices/uuid/pdf",
  "qr_code": null,
  "line_items": [
    {
      "line_number": 1,
      "description": "Product A",
      "quantity": 2,
      "unit_price": 50,
      "vat_rate": 15,
      "line_total": 115
    }
  ],
  "timeline": [
    {
      "status": "pending",
      "at": "2025-01-15T10:00:00.000Z"
    },
    {
      "status": "submitted",
      "at": "2025-01-15T10:00:05.000Z"
    },
    {
      "status": "reported",
      "at": "2025-01-15T10:05:00.000Z"
    }
  ]
}
GET/v1/invoices/:id/xml

Download signed XML

Returns the signed UBL XML for the invoice (from S3 archive or DB when S3 is disabled). Response is application/xml with Content-Disposition attachment. Use for ZATCA compliance and archiving. signed_xml_url is null when XML is not stored (e.g. older invoices).

Requires API Key (Bearer)

Code examples

const res = await fetch(`${BASE}/v1/invoices/${invoiceId}/xml`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const blob = await res.blob();
// Save or process blob (XML)

Success response (200)

(Binary XML stream with Content-Type: application/xml)

Error responses

  • 404
    {
      "message": "Signed XML not available for this invoice."
    }
GET/v1/invoices/:id/pdf

Download PDF

Returns a human-readable PDF of the invoice (seller, buyer if B2B, line items, totals). Use for display, printing, or sending to customers. For ZATCA compliance use the signed XML endpoint instead. Requires same Bearer token as other invoice endpoints.

Requires API Key (Bearer)

Code examples

const res = await fetch(`${BASE}/v1/invoices/${invoiceId}/pdf`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const blob = await res.blob();
// e.g. save or open in new tab
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'invoice.pdf';
a.click();

Success response (200)

(Binary PDF stream with Content-Type: application/pdf)