Makor API#

Issue legally compliant Israeli business documents over HTTP: tax invoices with real-time allocation numbers from the Israel Tax Authority, receipts with withholding tax, credit notes, PDF originals, and the statutory מבנה אחיד export. Everything is testable in a fully isolated sandbox before you go live.

Base URL (live)https://makor.izid.io/api/v1
Base URL (sandbox)https://sandbox.makor.izid.io/api/v1
Content typeapplication/json; charset=utf-8
AuthAuthorization: Bearer mk_live_… / mk_test_… / mk_op_live_…
Errorsapplication/problem+json (RFC 9457)
Schema/api/openapi.json · interactive at /api/docs

Issue an invoice in one call

Create an API key under Settings → API, then post a document. By default it is issued immediately: numbered, cleared with the Tax Authority when required, rendered and issued in a single request.

curl
# Sandbox host + sandbox key. Swap both together to go live.
curl -X POST 'https://sandbox.makor.izid.io/api/v1/documents' \
  -H 'Authorization: Bearer mk_test_1a2b3c4d5e6f...' \
  -H 'Idempotency-Key: invoice-2026-0001' \
  -H 'Content-Type: application/json' \
  -d '{
    "doc_type": 305,
    "issue_date": "2026-08-07",
    "customer_name": "לקוח לדוגמה",
    "customer_tax_id": "123456782",
    "lines": [
      { "description": "ייעוץ טכנולוגי", "quantity": 1, "unit_price": 6000 }
    ]
  }'
201 Created
HTTP/1.1 201 Created

{
  "id": "019fdad4-b4bb-70ce-94fd-8164faf6f426",
  "doc_type": 305,
  "doc_type_name_he": "חשבונית מס",
  "doc_type_name_en": "Tax Invoice",
  "series": "A",
  "doc_number": 1,
  "status": "issued",
  "issue_date": "2026-08-07",
  "customer_name": "לקוח לדוגמה",
  "customer_tax_id": "123456782",
  "subtotal": 6000.0,
  "discount_total": 0.0,
  "taxable_amount": 6000.0,
  "vat_rate_bp": 1800,
  "vat_amount": 1080.0,
  "total": 7080.0,
  "withholding_amount": 0,
  "currency": "ILS",
  "fx_rate": 1.0,
  "ils": null,
  "allocation_status": "approved",
  "allocation_number": "20260807123456782000000001",
  "is_sandbox": true,
  "language": "he",
  "parent_id": null,
  "open_balance": null
}
Node.js
const KEY = process.env.MAKOR_API_KEY;        // mk_live_… or mk_test_…
const BUSINESS = process.env.MAKOR_BUSINESS_ID;

// The key prefix and the base URL must always agree.
const BASE_URL = KEY.startsWith("mk_test_")
  ? "https://sandbox.makor.izid.io/api/v1"
  : "https://makor.izid.io/api/v1";

const res = await fetch(
  `${BASE_URL}/businesses/${BUSINESS}/documents`,
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      doc_type: 320,
      issue_date: new Date().toISOString().slice(0, 10),
      customer_name: "לקוח לדוגמה",
      lines: [{ description: "מנוי חודשי", quantity: 1, unit_price: 199 }],
      payments: [{ method: "card", amount: 234.82, card_brand: "Visa" }],
    }),
  }
);

if (res.status === 202) {
  const held = await res.json();
  console.log(held.rejection_code, held.decisions);
} else if (!res.ok) {
  const problem = await res.json();
  throw new Error(`${problem.code}: ${problem.detail}`);
}
Python
import os, uuid, httpx

KEY = os.environ["MAKOR_API_KEY"]
BUSINESS = os.environ["MAKOR_BUSINESS_ID"]

# The key prefix and the base URL must always agree.
BASE_URL = (
    "https://sandbox.makor.izid.io/api/v1" if KEY.startswith("mk_test_") else "https://makor.izid.io/api/v1"
)

with httpx.Client(
    base_url=BASE_URL,
    headers={"Authorization": f"Bearer {KEY}"},
    timeout=30,
) as client:
    r = client.post(
        f"/businesses/{BUSINESS}/documents",
        headers={"Idempotency-Key": str(uuid.uuid4())},
        json={
            "doc_type": 305,
            "issue_date": "2026-08-07",
            "customer_tax_id": "123456782",
            "customer_name": "לקוח לדוגמה",
            "lines": [
                {"description": "ייעוץ", "quantity": 1, "unit_price": 6000}
            ],
        },
    )

    if r.status_code == 202:
        held = r.json()
        client.post(
            f"/businesses/{BUSINESS}/documents/{held['id']}/allocation-decision",
            json={"choice": "continue"},
        )
    else:
        r.raise_for_status()

Authentication#

Makor uses opaque API keys as bearer tokens. There is no token-exchange step and nothing to refresh — the key you create is the credential you send on every request.

header
Authorization: Bearer mk_live_9f8e7d6c5b4a3928170615243342516071829304
PrefixEnvironmentBehaviour
mk_live_ProductionValid only against https://makor.izid.io/api/v1. Creates real documents: consumes your legal numbering, reaches the Tax Authority, appears in reports and the מבנה אחיד export.
mk_test_SandboxValid only against https://sandbox.makor.izid.io/api/v1. Creates test documents only — see Sandbox.
mk_op_live_Production (operator)An operator key: bound to an operator rather than a single business, and reaches every business that has granted it access. See Operators.
mk_op_test_Sandbox (operator)An operator key for the sandbox. Valid only against https://sandbox.makor.izid.io/api/v1.
Keys are shown once
The full key is returned only in the response that creates it; Makor stores just a SHA-256 digest. To rotate, create a new key and revoke the old one — revocation takes effect immediately (subsequent requests return 401).

Failure modes

401 Unauthorized
MAKOR-401
Missing, malformed, unknown or revoked key.
404 Not Found
MAKOR-404
The key is valid but belongs to a different business than the Makor-Business header — or, for an operator key, that business has not granted you access or has revoked it. Cross-tenant access is reported as “not found” rather than “forbidden” so key holders cannot probe for the existence of other businesses, or for who is a customer of whom.
403 Forbidden
MAKOR-403
Authenticated, but the key lacks the scope the endpoint requires.

Scopes & permissions#

Every endpoint declares exactly one scope. Keys receive the least privilege you ask for; omitting scopes grants the maximum your role allows.

ScopeGrants
documents:readList and read documents, download PDFs
documents:writeCreate, issue, cancel, delete drafts, allocation decisions, share links
customers:readList and read customers and their e-delivery consent
customers:writeCreate, update, deactivate customers; record consent
items:readList catalogue items
items:writeCreate, update, deactivate items
scans:readList and read scans, download the page image
scans:writeUpload, approve, reject and delete scans
reports:readIncome, VAT and withholding reports
export:readGenerate and download מבנה אחיד exports
For operators, scopes are an intersection
An operator key has a different effective permission on every business: key scopes ∩ granted scopes. A business may grant only documents:read to an operator whose key includes write, and the result is read-only on that business. Do not infer from the key’s own scopes — GET /operator/businesses returns the effective scopes per business.
Accountant keys are capped server-side
A key created by a user whose membership role is accountant is reduced to read-only scopes regardless of what the request asks for. The response echoes the scopes actually granted — read them back rather than assuming.

Sandbox#

Experiment without ever producing a real invoice. The sandbox has its own API base URL and its own keys, so the two environments cannot be confused — yet it is still your own account, with no separate signup.

EnvironmentBase URLKey
Livehttps://makor.izid.io/api/v1mk_live_…
Sandboxhttps://sandbox.makor.izid.io/api/v1mk_test_…
The URL and the key must match
An mk_test_ key against the live URL is rejected with 403, and so is an mk_live_ key against the sandbox URL — each with a message pointing at the right base URL. That is why you cannot issue a real invoice by accident while developing: you would have to get both wrong at once.
From the API

Create a key with "sandbox": true and call the sandbox base URL. Every request operates on test data — no extra parameters.

From the app

Settings → Sandbox → Enter sandbox mode. An orange banner marks the session and everything you create is a test document.

What isolation actually means

Separate numbering
guaranteed
Test documents draw from their own sequence per document type. Your legal, gapless live numbering is never advanced by an experiment.
No Tax Authority traffic
guaranteed
Allocation requests for test documents are answered by a deterministic simulator, never sent to the Authority — even in production.
Excluded from the books
guaranteed
Test documents never appear in income, VAT or withholding reports, nor in the מבנה אחיד export.
Visibly marked
guaranteed
Every test PDF carries a diagonal "SANDBOX — אינו מסמך חשבונאי" watermark, and the API returns is_sandbox: true.
Two-way blindness
guaranteed
A live key returns 404 for a test document and vice versa; list endpoints only ever return one environment.

Deterministic allocation triggers

To exercise every Tax Authority outcome on demand, the sandbox picks its response from the agorot of the pre-VAT amount — its two decimals. This lets you build and test the rejection flow without waiting for a real refusal.

Amount ends withSimulated outcomeHTTP
…60Held for review, code 460 — the four-way decision flow202
…61Unapproved invoice pending decision, code 461202
…03Technical failure — issued with failed_retro_pending201
anything elseApproved with a simulated 26-digit allocation number201
Example: unit_price: 6000.60 (₪6,000.60) triggers a code 460 hold, while 6000.00 is approved.

Conventions#

A few rules hold everywhere in the API. Getting these right removes most integration surprises.

Money
number (₪)
Monetary values are shekels with up to two decimals. ₪1,180.00 is 1180.0; more than two decimals is refused rather than rounded. Never send floats, and never send a VAT amount — VAT is computed server-side.
VAT
server-computed
The prices you send are pre-VAT unless you add prices_include_vat: true, which reads every price and discount in the request as carrying VAT and divides it back out — the document then totals exactly what you sent. Applied at the statutory rate in force on issue_date (18% since 2025-01-01), computed once per rate group at document level with half-up rounding — deliberately not per line, to avoid agora drift. Exempt dealers and NPOs get zero.
Dates
YYYY-MM-DD
Plain calendar dates, no timezone. Timestamps in responses are ISO-8601 UTC; numbering and reporting periods follow Israel local time.
Identifiers
UUIDv7
Time-ordered UUIDs, so lexicographic order equals creation order — this is what makes cursor pagination stable.
Tax IDs
string, 9 digits
ח.פ / מספר עוסק / ת.ז as exactly nine digits including the check digit, validated on business creation (MAKOR-BIZ-002).
Hebrew text
UTF-8
Send Hebrew as-is in JSON. Rendering handles RTL, and the מבנה אחיד export transcodes to ISO-8859-8 as the standard requires.

Errors#

Errors follow RFC 9457 application/problem+json. Branch on the stable code, never on the human-readable text. Every error carries an English detail and a Hebrew detail_he that is safe to show end users.

problem+json
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/problem+json

{
  "type": "https://makor.izid.io/dev#errors",
  "title": "Payment lines (5000) must sum to document total (11800)",
  "status": 422,
  "code": "MAKOR-DOC-011",
  "detail": "Payment lines (5000) must sum to document total (11800)",
  "detail_he": "סכום אמצעי התשלום חייב להיות שווה לסכום המסמך (כולל שורת ניכוי במקור)"
}
StatusMeaning
400 / 422Validation or business-rule violation — see the code index below
401Authentication failed (missing, unknown or revoked key)
403Authenticated but missing the required scope, or a read-only role
404Resource does not exist, or belongs to another business/environment
409State conflict — e.g. issuing a document that is no longer a draft
202Not an error: the invoice was held by the Tax Authority and awaits your decision
Field-level validation performed by the schema layer (types, patterns, lengths) returns the framework’s standard 422 with a detail array instead of a MAKOR-* code. Business rules always use MAKOR-*.

Idempotency#

Document creation is the one call you must never accidentally repeat — a duplicate would consume a legal document number that cannot be reused.

Idempotency-Key
header, stringoptional
Send a unique value (a UUID works well) on POST /documents. Retries with the same key replay the original stored response — same document, same number — instead of creating a second document. Keys are retained for 48 hours.
SituationResult
Same key, same body, completedOriginal response replayed verbatim
Same key, different body422 MAKOR-IDEM-001 — a key may describe only one request
Same key while the first request is still running409 MAKOR-IDEM-002 — retry shortly
Without an idempotency key, a network timeout leaves you unable to tell whether the invoice was issued. Always send one in production.

Pagination#

List endpoints that can grow without bound use cursor pagination, which stays correct even while new documents are being issued.

response
{
  "items": [ /* … */ ],
  "next_cursor": "019fdad4-b4bb-70ce-94fd-8164faf6f426"
}
limit
integer= 50
Page size, maximum 200.
cursor
string (uuid)optional
Pass the previous response’s next_cursor to fetch the next page. Results are newest-first; a null cursor means you have reached the end. An unparsable cursor returns 422 MAKOR-PAGE-001.
Customers and items use a simple limit + q search instead of cursors — they are small, human-curated collections.

Allocation numbers (מספר הקצאה)#

Under the חשבוניות ישראל reform, a tax invoice above the statutory threshold must carry a clearance number obtained from the Tax Authority in real time. Without it the buyer cannot deduct input VAT, and since August 2025 the expense is not deductible for income tax either. Makor performs this exchange inside the issue call.

When it applies

Document type
305 / 320
Tax invoice and invoice-receipt only. Credit notes and non-VAT documents are never cleared.
Amount
≥ threshold
Pre-VAT amount at or above the threshold in force on the issue date — ₪5,000 since 2026-06-01 (₪10,000 from 2026-01-01, ₪20,000 from 2025-01-01, ₪25,000 from 2024-05-05).
Counterparty
B2B
A business customer: customer_tax_id becomes mandatory above the threshold (MAKOR-DOC-040).

Outcomes

HTTPallocation_statusWhat happened
201approvedCleared. allocation_number holds the full 26-digit confirmation; the PDF prints its nine right-most digits under “מספר הקצאה”.
201not_requiredBelow threshold, B2C, or a document type outside the mandate.
201failed_retro_pendingThe Authority was unreachable. Regulation permits issuing anyway; ask for the number afterwards with POST /documents/{document_id}/allocation-request, up to a year from the invoice date.
202rejectedHeld for review (code 460 or 461). The document is numbered but not issued — it stays pending until you choose a path.
202 Accepted
HTTP/1.1 202 Accepted

{
  "id": "019fdad4-...",
  "status": "pending",
  "doc_number": 42,
  "allocation_status": "rejected",
  "allocation_decision_required": true,
  "rejection_code": 460,
  "rejection_message": "Data is correct but invoice was not approved",
  "decisions": ["cancel", "continue", "reverse_charge", "object"]
}

Resolving a held invoice

These four options are the statutory alternatives. You must pick one — the document cannot be left in limbo — and the decision is reported back to the Authority.

choiceEffectConsequence
cancelDocument becomes cancelledThe number is retained as a cancelled document — never reused, so the sequence stays gapless.
continueIssued without an allocation numberThe PDF prints the mandatory caption "אין לנכות מס תשומות בגין חשבונית זו" — your customer cannot deduct input VAT.
reverse_chargeRe-submitted as היפוך חיובZero-VAT self-billing: the customer reports the transaction. Receives its own allocation number.
objectFormal objection (השגה) filedDocument stays pending with allocation_status: objection until the Authority rules.
curl
curl -X POST 'https://makor.izid.io/api/v1/documents/{document_id}/allocation-decision' \
  -H 'Authorization: Bearer mk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{ "choice": "continue" }'
Why a rejected invoice keeps its number
The number is assigned before the Authority is contacted, because the clearance request must carry the invoice number. Under הוראות ניהול ספרים a cancelled document remains in the sequence rather than freeing its number — which is exactly what keeps the series gapless and auditable.

The document object#

Returned by every document endpoint. Amounts are shekels; monetary fields are always present, even when zero.

id
string (uuid)required
Stable identifier.
Makor-Business
headerrequired
A key bound to one business names nothing — the business comes from the key. An operator key names one in a Makor-Business header, by our uuid or by ח.פ / מספר עוסק. A tax ID matching more than one business you can reach is refused with 409 MAKOR-BIZ-005 rather than guessed.
doc_type
integeroptional
Type code — see document types. These are the same codes the מבנה אחיד standard uses. Give exactly one of doc_type or doc_kind.
payment_method
stringoptional
Shorthand for “the whole document was paid this way”: cash · transfer · card · other. The amount comes from the document total, or for a receipt from the invoices it closes. Cannot be sent alongside payments (422 MAKOR-DOC-014), and returns 422 MAKOR-DOC-013 when there is no amount to infer. check is recognised but refused: מבנה אחיד requires the cheque number, so use payments for one.
external_ref
string ≤100optional
Your own identifier for the document — an order number in your system, say. Stored and echoed untouched, never interpreted, and not required to be unique.
draft
booleanoptional
Issuing is the default. Send true to create a draft and issue it later through POST /documents/{document_id}/issue. Note that status on the response may come back pending even when you asked to issue, if the Tax Authority holds it at the allocation gate — which is why this is an intent rather than a status.
send_email
boolean | null= null
Whether this document is emailed to the customer as it is issued, overriding the business’s own setting in both directions. null — the default, and what every request written before this field existed means — leaves the decision to the business. true on a customer with no stored address returns 422 MAKOR-MAIL-002, and on a draft request 422 MAKOR-DOC-016 — both before any document exists.
doc_kind
stringoptional
A readable alias for the same code — tax_invoice rather than 305. Fully equivalent to doc_type. Sending both returns 422 MAKOR-DOC-003 even when they agree, because otherwise a real contradiction would pass silently the next time. An unknown alias returns 422 MAKOR-DOC-004 and lists the known ones.
doc_type_name_he / _en
stringrequired
Human-readable type name, ready to print.
series
stringrequired
Numbering series, default "A".
doc_number
integer | nullrequired
Assigned at issuance and immutable thereafter; null while the document is a draft.
status
stringrequired
draft · pending · issued · cancelled.
customer_id / customer_name / customer_tax_id
string | nullrequired
Customer snapshot frozen at issuance — later edits to the customer record never alter an issued document.
issue_date / due_date
date | nullrequired
Calendar dates.
subtotal
number (₪)required
Sum of line totals before any document-level discount, excluding VAT.
discount_total
number (₪)required
Document-level discount.
taxable_amount
number (₪)required
VAT base after discount allocation — this is the figure compared against the allocation threshold.
vat_rate_bp
integerrequired
Rate in basis points; 1800 = 18%. Zero when the document carries no VAT.
vat_amount
number (₪)required
Computed VAT.
total
number (₪)required
Grand total including VAT.
withholding_amount
number (₪)required
Sum of payment lines whose method is withholding (ניכוי מס במקור).
currency
stringrequired
ISO 4217 code every amount above is written in. Omitting it on a request means shekels — exactly what a request written before this field existed means. Any other currency requires fx_rate.
fx_rate
number | nullrequired
The shekel value of one unit on the issue date. Stored on the document and never revised — it is part of what the document declares. Never fetched server-side at issuance; see GET /fx/rates. A shekel document carries 1.
ils
object | nullrequired
The same document in shekels at the stored rate: subtotal · discount_total · taxable_amount · vat_amount · total · withholding_amount. null for a shekel document, where it would repeat the numbers beside it. This is the side the Tax Authority sees — מבנה אחיד, the VAT reports, and the allocation threshold.
allocation_status
stringrequired
See allocation statuses.
allocation_number
string | nullrequired
Full 26-digit confirmation number. Print the last nine digits.
can_request_allocation
booleanoptional
Whether POST /documents/{id}/allocation-request would be accepted for this document — state, type, the customer's tax id and the year the Authority allows, weighed server-side so you need not restate the rule. The one thing it does not check is whether the business is connected to the Authority.
language
stringrequired
he or en — controls document rendering.
parent_id
string | nullrequired
For credit notes: the invoice being credited.
is_sandbox
booleanoptional
true for test documents.
open_balance
number (₪) | nulloptional
Only on single-document reads of issued invoices and proformas: total minus everything already closed by receipts and credit notes.
lines[] / payments[]
arrayoptional
Included on single-document reads only, not in list responses.

Enumerations#

Fixed vocabularies used throughout the API.

Document types

Codedoc_kindHebrewEnglishNotes
10price_quoteהצעת מחירQuoteNo bookkeeping effect
100orderהזמנהOrderNo bookkeeping effect
200delivery_noteתעודת משלוחDelivery note
300proformaחשבונית עסקהProforma invoiceBills without triggering VAT — the cash-basis pattern
305tax_invoiceחשבונית מסTax invoiceAllocation number above threshold
320tax_invoice_receiptחשבונית מס/קבלהInvoice-receiptRequires payments; allocation applies
330credit_noteחשבונית זיכויCredit noteRequires parent_id; never cleared
400receiptקבלהReceiptRequires payments; closes invoices via linked_invoices
405donation_receiptקבלה על תרומהDonation receiptNPOs only, separate series
Entity type limits which codes you may issue
עוסק פטור — 10, 100, 200, 300, 400 only; cannot issue tax invoices at all (MAKOR-DOC-001).
עוסק מורשה / חברה / שותפות — everything except 405.
עמותה — 10, 300, 400, 405 only; no VAT.

Document status

ValueMeaning
draftEditable, unnumbered, no legal effect
pendingNumbered and frozen; awaiting clearance or a decision
issuedFinal and immutable
cancelledVoided; number retained in the sequence

Allocation status

ValueMeaning
not_requiredOutside the mandate
pendingRequest in flight
approvedCleared, number stored
rejectedHeld — decision required
rejected_continueIssued without clearance, legal caption printed
reverse_chargeIssued as היפוך חיוב
objectionObjection filed, awaiting ruling
failed_retro_pendingTechnical failure; the number is asked for after the fact

Payment methods

ValueExtra fields
cash—
chequeRequires cheque_number; bank_code, branch, account, paid_date recommended
cardcard_brand, card_last4 (exactly 4 digits), installments
bank_transfertransfer_ref
appBit, Paybox and similar
withholdingניכוי מס במקור — not money received, but it discharges the debt and counts toward the total
other—

VAT treatment

ValueMeaning
standardStandard-rated (18%)
exemptExempt supply — excluded from the VAT base
zeroZero-rated, e.g. exports

Entity types

ValueHebrew
osek_paturעוסק פטור
osek_mursheעוסק מורשה
companyחברה בע"מ
partnershipשותפות
amutaעמותה / מלכ"ר

Documents#

The core of the API. All paths are relative to the base URL and scoped to a business.

POST/documentsdocuments:write

Runs the full issuance pipeline: validation → gapless numbering → Tax Authority clearance → PDF render and digital signature. Returns 201, or 202 when the Authority holds the invoice. Send "draft": true to stop after validation and leave an unnumbered draft.

Query & headers

Makor-Business
headeroptional
An operator key names the business it is acting for — our uuid or the tax ID.
sandbox
boolean= false
Create a test document. Ignored for API keys — the key's own environment always wins.
Idempotency-Key
headeroptional
Strongly recommended on every write — a repeat returns the original response.

Body

doc_type
integerrequired
One of the document type codes.
issue_date
date= today
Defaults to the current date in Israel. Determines the VAT rate and the allocation threshold applied.
customer_id
string (uuid)optional
Existing customer; name, tax ID and address are copied onto the document at issuance.
customer_name
stringoptional
One-off customer, or an override of the stored name.
customer_tax_id
string (9 digits)optional
Mandatory above the allocation threshold (MAKOR-DOC-040).
lines[]
arrayoptional
description (required, ≤500), quantity (required, > 0), unit_price (required, ≥ 0), unit, discount, vat_treatment, item_id. Required for every type except pure receipts.
payments[]
arrayoptional
method and amount required. Mandatory for 320, 400 and 405, and must sum exactly to the document total (MAKOR-DOC-011).
linked_invoices[]
arrayoptional
{ invoice_id, amount } — invoices this receipt closes, fully or partially. Validated against each invoice’s open balance under a row lock, so concurrent receipts cannot over-close.
parent_id
string (uuid)optional
Required for credit notes (330): the invoice being credited.
document_discount
number (₪)= 0
Discount on the whole document, allocated proportionally across the VAT base.
prices_include_vat
boolean= false
Line prices (and discounts) already carry VAT, and the server divides it back out. Stored and exported amounts stay VAT-exclusive either way — מבנה אחיד is defined that way — while the printed document shows the prices as entered. The document totals exactly what was sent.
due_date
dateoptional
Payment due date printed on the document.
language
string= "he"
he or en — chooses the rendering language of the PDF.
series
string= "A"
Alternate numbering series (e.g. per branch). Each series is independently gapless.
notes / footer_text
stringoptional
Free text printed on the document.
receipt closing an invoice, with withholding
{
  "doc_type": 400,
  "issue_date": "2026-08-07",
  "customer_id": "019fda...",
  "payments": [
    { "method": "bank_transfer", "amount": 1121.00, "paid_date": "2026-08-07" },
    { "method": "withholding",   "amount": 59.00 }
  ],
  "linked_invoices": [
    { "invoice_id": "019fdac1-...", "amount": 1180.00 }
  ]
}
Why payment lines must sum to the total
Withholding tax is not money you received, but it does discharge the debt. Recording it as a payment line of method withholding is how the receipt balances — above, ₪1,121.00 arrived by transfer and ₪59.00 was withheld at source, together closing an ₪1,180.00 invoice.
POST/documents/{document_id}/issuedocuments:write

Issues an existing draft — same pipeline and same 201/202 semantics as an ordinary create. Issuing anything that is not a draft returns 409 MAKOR-DOC-060, which is also what a duplicate request looks like.

POST/documents/{document_id}/allocation-decisiondocuments:write
choice
stringrequired
One of cancel, continue, reverse_charge, object.
reason
string ≤500optional
Stored on the document when cancelling.

Valid only while the document is pending with allocation status rejected or objection; otherwise 409 MAKOR-ALLOC-001. If a reverse-charge resubmission is itself refused you get 409 MAKOR-ALLOC-002.

POST/documents/{document_id}/allocation-requestdocuments:write

Asks for an allocation number for a document that is already issued — the other side of the gate. The gate asks while issuing; this asks afterwards, up to a year from the invoice date. It covers the three ways an invoice exists without a number: failed_retro_pending (the Authority was unreachable), rejected_continue (issued without one after a refusal) and not_required (under the threshold — the Authority allocates for these too). A document that already has a number is refused with 409 MAKOR-ALLOC-004: one invoice, one allocation. can_request_allocation on the document says in advance whether the call will be accepted.

curl
curl -X POST 'https://makor.izid.io/api/v1/documents/{document_id}/allocation-request' \
  -H 'Authorization: Bearer mk_live_...'
200 OK
HTTP/1.1 200 OK

{
  "outcome": "approved",
  "rejection_code": null,
  "rejection_message": null,
  "document": {
    "id": "019fdad4-...",
    "status": "issued",
    "doc_number": 42,
    "allocation_status": "approved",
    "allocation_number": "20260807123456782000000042",
    "allocation_number_short": "000000042",
    "can_request_allocation": false
  }
}
A 200 is not a yes
All three answers come back as 200, and outcome says which: approved — the document now has a number; rejected — the Authority answered about this invoice and said no (rejection_code 460/461); failed — no answer was obtained. The last two return the document unchanged: it was lawfully issued, the customer holds it, and a refusal does not reopen the four-way decision. The signed original stays unchanged. Use ?copy=true for an unsigned current copy with the later allocation number and updated allocation caption.
GET/documentsdocuments:read

Query & headers

doc_type
integeroptional
Filter by type code.
status
stringoptional
draft · pending · issued · cancelled.
from_date / to_date
dateoptional
Filter on issue_date, inclusive.
limit
integer= 50
Maximum 200.
cursor
stringoptional
From the previous next_cursor.
sandbox
boolean= false
Session callers switch environment; API keys are fixed to their own.

Returns { items, next_cursor }, newest first. List items omit lines, payments and open_balance — fetch a single document for those.

GET/documents/{document_id}documents:read

The full document, including lines[], payments[] and — for issued invoices and proformas — open_balance.

POST/documents/{document_id}/canceldocuments:write
reason
string ≤500optional
Required for issued tax invoices; recorded in the audit log.
The number is retained — cancellation never frees it for reuse. A document that already has receipts or credit notes attached cannot be cancelled at all (409 MAKOR-DOC-064); issue a credit note instead. If the original already reached the customer, a credit note is required. Issued tax invoice cancellation requires original_not_delivered=true and not_reported=true; known sharing or sending blocks it (MAKOR-DOC-066).
DELETE/documents/{document_id}documents:write

Deletes a draft. Anything already numbered returns 409 MAKOR-DOC-065 — issued documents are immutable and are cancelled or credited, never deleted.

GET/documents/{document_id}/pdfdocuments:read

Returns application/pdf. New originals are signed and stored at finalization; every later call returns those exact bytes, preserving the original file unchanged. Add ?copy=true for a copy (העתק) rendering. Drafts have no PDF (409 MAKOR-PDF-001).

POST/documents/{document_id}/sharedocuments:write

Creates — or returns the existing — public link for the customer: { token, url }. The page lives at /d/{token}, needs no authentication, is served noindex, and offers the PDF at /d/{token}/pdf. Only issued or cancelled documents can be shared (409 MAKOR-SHARE-001).

Customers#

GET/customerscustomers:read

Query & headers

q
stringoptional
Case-insensitive search across name, tax ID and email.
limit
integer= 50
Maximum 200.
POST/customerscustomers:write
name
string ≤200required
Display name.
tax_id
string (9 digits)optional
Needed later if you invoice this customer above the allocation threshold.
email / phone
stringoptional
Used for document delivery.
address_street / _house / _city / _zip
stringoptional
Printed on documents.
country_code
string (2)= "IL"
ISO 3166-1 alpha-2.
withholding_rate_bp
integer 0–5000= 0
Default withholding rate in basis points (500 = 5%), used to pre-fill receipts.
notes
string ≤1000optional
Internal note.
GET/customers/{customer_id}customers:read
PATCH/customers/{customer_id}customers:write

Partial update. Editing a customer never changes documents already issued to them — those carry a frozen snapshot.

DELETE/customers/{customer_id}customers:write

Soft-deletes (deactivates). History is preserved for the statutory retention period.

Items#

An optional catalogue for pre-filling document lines.

GET/itemsitems:read

Query & headers

q
stringoptional
Name search.
limit
integer= 100
Maximum 500.
POST/itemsitems:write
name
string ≤200required
Item name.
name_en
stringoptional
Used on English-language documents.
sku
string ≤20optional
Internal catalogue number (מק"ט).
unit
string= "יחידה"
Unit of measure.
unit_price
number (₪)= 0
Default price.
vat_treatment
string= "standard"
standard · exempt · zero.
description
string ≤500optional
Long description.
PATCH/items/{item_id}items:write
DELETE/items/{item_id}items:write

Numbering#

Sequences are per business, document type and series, and are strictly gapless — the law requires continuity, no reuse within a tax year, and that cancelled documents keep their number.

GET/numbering

Lists live sequences (sandbox sequences are private): doc_type, doc_type_name_he, series, next_number, configured_start, locked.

PUT/numbering
doc_type
integerrequired
Type whose sequence you are configuring.
starting_number
integer ≥ 1required
First number to be issued. Migrating from a paper book whose last receipt was 143? Set 144.
series
string= "A"
Series to configure.
Owner-only, and permitted only before the first document is issued in that sequence — afterwards it is locked (409 MAKOR-NUM-002), because changing it retroactively would break the legally required continuity.

Foreign currency#

A document is denominated in one currency. Omitting `currency` means shekels at a rate of 1 — exactly what a request written before the field existed means.

Any currency other than the shekel requires fx_rate: the shekel value of one unit on the issue date. The rate is part of what the document declares, so it travels on the request rather than being fetched server-side at issuance — and it is frozen with the document. The response carries the document as invoiced and as reported, under ils.

Everything the state sees reads the shekel side: the מבנה אחיד export files shekel amounts (the original rides along in fields 1217/1218), the VAT and income reports sum in shekels, and the ₪5,000 allocation threshold is a shekel threshold — a $1,500 invoice crosses it.

Two rules follow from a document having one currency: an amount in a currency with no minor unit must be whole (MAKOR-CUR-006 — ¥100.50 is not a price), and a receipt closes only an invoice in the same currency (MAKOR-LINK-006). There is no rate at which a dollar link subtracts from a shekel balance.

GET/fx/ratesdocuments:read

Query & headers

currency
stringrequired
ISO 4217 code, e.g. USD.
on
dateoptional
Defaults to today in Israel.

The Bank of Israel rate for one currency on one date, normalised to a single unit (the Bank publishes some currencies per 100). 404 means the Bank did not publish that date — a weekend or a holiday — and 503 means the feed is unreachable. Both are answers rather than failures: send the rate you have.

GET/fx/currenciesdocuments:read

The codes that may be used, each with its decimal places and display symbol.

Scans (incoming documents)#

Documents the business received. A scan is not a document: it takes no number, never enters the מבנה אחיד export, and cannot become one — it is filed evidence that feeds the expense and input-VAT reports.

POST/scansscans:write

Multipart with a single file field. Optional: direction (expense/income), year, month, external_ref. JPG, PNG or PDF up to 50MB; a multi-page PDF becomes one scan per page. Answers 202 with scan_ids, or 200 with {"scan_ids": [], "status": "duplicate"} when the same bytes were already filed — so retrying an upload is always safe.

GET/scans/{id}scans:read

Extraction runs in the background. Poll until status leaves processing, or subscribe to scan.ready. extraction.uncertain_fields is the model flagging its own reading; extraction.warnings is the server’s arithmetic disagreeing. Both mark what to check; neither blocks filing.

POST/scans/{id}/approvescans:write

Nothing counts towards a report until it is approved. Send only what changed. Approval enforces what review only warned about: subtotal plus VAT must equal the total (MAKOR-SCAN-005), a total is required (-009), and a foreign-currency scan needs fx_rate (-010).

GET/scans/{id}/imagescans:read

When extraction is unavailable — the switch is off, or no model is configured — the upload still succeeds and the scan lands in ready with empty fields and an extraction.skipped_reason. Treat that as a normal case, not an error.

Emailing a document#

One call puts an issued document in the customer’s inbox with the original original attached — the same bytes GET /documents/{id}/pdf returns, not a fresh render.

POST/documents/{id}/senddocuments:write

Both fields are optional. Omitting to uses the address on the customer record; an explicit one wins. With neither you get MAKOR-MAIL-002 rather than a silent success. The From is מקור’s — that is what lets the message pass DKIM without every business publishing DNS records — with the business name in the subject and body. Reply-To is the business’s own address, so a reply reaches them.

GET/documents/{id}/deliveriesdocuments:read

status is sent, failed (with an error saying why — safe to retry) or captured — מצב ניסוי, where the message is built and logged but never posted. Every send fires a document.emailed webhook.

GET/email-deliveriesdocuments:read

Everything this business sent lately, newest first. limit up to 100.

A business with email_auto_send on emails every document it issues, wherever the customer record holds an address. It runs after the response, and a failed send never un-issues the document — it has a number and is in the books either way. The failure is a failed delivery row to retry, not a rollback. An operator sets it when registering the business (POST /operator/businesses); changing it afterwards is the owner’s, through PATCH /businesses/{id}.

send_email on the call that issues a document overrides the business setting in both directions: true sends with the setting off, false holds it back with the setting on, and leaving it out leaves the decision to the business. For a draft you issue later, send it on POST /documents/{id}/issue. A request that cannot be honoured is refused before any document exists — 422 MAKOR-MAIL-002 for a customer with no address, 422 MAKOR-DOC-016 for a draft — because that is the only moment a refusal costs no document number. After it the usual rule returns: a send that fails is a delivery row, never an un-issued document.

New PDFs have an embedded cryptographic signature using a separate key for each business. The certificate is internally managed, without external CA certification or a trusted timestamp. Historical documents are not re-signed. Copies and HTML previews are unsigned.

Reports#

Aggregations over issued live documents. Credit notes are subtracted; sandbox documents are never counted.

GET/reports/incomereports:read

Query & headers

from_date
daterequired
Inclusive.
to_date
daterequired
Inclusive.

Returns monthly[] (month, total, vat), by_type[] and total.

GET/reports/vatreports:read

Output VAT per month: periods[] with taxable and output_vat, plus total_output_vat.

GET/reports/withholdingreports:read

Tax withheld at source per customer — the figures behind your annual טופס 806 reconciliation: customers[] and total_withheld.

Uniform format (מבנה אחיד)#

The statutory bookkeeping export defined by הוראות ניהול ספרים, spec v1.31 — the files an auditor or your accountant will ask for.

POST/exports/unified-formatexport:read
from_date
daterequired
Inclusive.
to_date
daterequired
Inclusive; must not precede from_date (MAKOR-EXP-001).

Builds INI.TXT and BKMVDATA.TXT (fixed-width, ISO-8859-8, CRLF) inside the mandated OPENFRMT/{vat}.{yy}/{MMDDhhmm} folder structure and returns export_id, folder_name, record_counts and the closing_report — per document type, the count and total your accountant reconciles against.

GET/exports/unified-format/{export_id}/downloadexport:read

Returns the ZIP archive. Accountant-role members can export even though they cannot issue anything — that is the whole point of the role.

Webhooks#

Subscribe to events instead of polling. Deliveries are HMAC-signed and every attempt is logged.

POST/webhooks
url
string (https)required
Destination endpoint.
events
string[]required
At least one event name; unknown names are rejected with 422 MAKOR-WH-001.

Owner-only. The response contains the signing secret — shown once.

EventEmittedPayload
document.issuedYesdocument_id, doc_type, doc_number, total, allocation_status
allocation.rejectedYesdocument_id, rejection_code
document.emailedYesdocument_id, to, status
scan.readyYesscan_id, status, direction, external_ref
scan.failedYesscan_id, status, direction, external_ref
scan.approvedYesscan_id, total, currency
document.cancelledReserved—
allocation.approvedReserved—
allocation.retro_assignedReserved—
payment.linkedReserved—
export.readyReserved—
ita.authorization_expiringReserved—
Events marked Reserved can be subscribed to today but are not emitted yet — subscribing now means you receive them as soon as they ship, with no change on your side.
delivery
POST https://your-app.example/hooks/makor
X-Makor-Event: document.issued
X-Makor-Signature: t=1786000000,v1=6f2b...c91

{
  "event": "document.issued",
  "created_at": "2026-08-07T12:31:07.481Z",
  "data": {
    "document_id": "019fdad4-...",
    "doc_type": 305,
    "doc_number": 42,
    "total": 7080.0,
    "allocation_status": "approved"
  }
}

Verifying a signature

Compute HMAC-SHA256(secret, "{t}.{raw body}") over the raw request body — parsing and re-serializing the JSON first changes the bytes and breaks the comparison. Compare in constant time and reject stale timestamps.

Node.js
import crypto from "node:crypto";

export function verify(rawBody, header, secret, toleranceSec = 300) {
  const parts = Object.fromEntries(
    header.split(",").map((kv) => kv.split("=").map((s) => s.trim()))
  );
  const timestamp = Number(parts.t);
  if (!Number.isFinite(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSec) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1, "hex")
  );
}
Python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(kv.split("=", 1) for kv in header.split(","))
    ts = int(parts["t"])
    if abs(time.time() - ts) > tolerance:
        return False
    expected = hmac.new(
        secret.encode(),
        f"{ts}.".encode() + raw_body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])
GET/webhooks
GET/webhooks/{webhook_id}/deliveries

The last 50 attempts with event_type, status, response_status and attempt — the first place to look when an integration goes quiet.

DELETE/webhooks/{webhook_id}
Respond fast, then work
Deliveries time out after 5 seconds. Acknowledge with 2xx immediately and process asynchronously; treat every event as possibly duplicated and key your handler on document_id.

API keys#

Keys are managed by signed-in users, not by other keys.

POST/api-keys
name
string ≤100required
Label shown in the dashboard.
scopes
string[]optional
Defaults to the maximum your role allows. Unknown scopes return 422 MAKOR-KEY-001.
sandbox
boolean= false
Mint a mk_test_ key bound to sandbox data.

Returns key (the full secret, once), key_id, the granted scopes and sandbox.

GET/api-keys

Metadata only — key_id, name, scopes, sandbox, last_used_at, revoked. Secrets are never retrievable.

DELETE/api-keys/{key_id}

Revokes immediately. Owner or employee role required.

Operators#

An operator is a software vendor that issues documents on behalf of other businesses — a POS, a booking system, a SaaS platform. One key, all of your customers.

The permission model

An operator is never a member of the businesses it serves. It holds one grant per business, which the business issues and can revoke at any moment, and it has no administrative access at all: not to business details, team members, numbering, or minting keys. The grant also caps what it may do — see Scopes.

How it fits together

StepPerformed byCall
Register as an operatorYou, signed inPOST /operators
Mint an operator keyOperator admin, signed inPOST /operators/{operator_id}/api-keys
Subscribe to connection eventsOperator admin, signed inPOST /operators/{operator_id}/webhooks
Grant accessThe business ownerPOST /businesses/{business_id}/operators
— or — register a new businessYour keyPOST /operator/businesses
Discover your businessesYour keyGET /operator/businesses
Issue documentsYour keyPOST /documents + Makor-Business
The rest of the API is unchanged
There is no separate operator API. Same calls, same request bodies, same errors — the only difference is that an operator key is not bound to one business, so it names the one it is acting for in the Makor-Business header (business ID or tax ID). An existing single-business integration becomes multi-business by swapping the key, adding the header, and looping over /operator/businesses.

Registering a business you bring

POST/operator/businesses

Everything above assumes the business already has a Makor account and granted you access. When you are bringing a customer of your own, this call registers them and grants you access in the same operation. Authenticated with your operator key alone. Registering the A tax ID is registered once: if it is already yours you get409 MAKOR-REG-001 naming the business, and if it belongs to someone else,409 MAKOR-REG-004 — the business has to connect you from its own account. There is no scopes field: the grant opens fully, and the owner narrows or revokes it once they claim the business.

legal_name
string 2–200required
The name registered with the Tax Authority — not a trading name. Printed on every document.
tax_id
string (9 digits)required
Israeli business/company number. The check digit is verified (422 MAKOR-BIZ-002).
entity_type
enumrequired
osek_patur · osek_murshe · company · partnership · amuta. Determines VAT liability and available document types.
owner_email
emailrequired
The owner. A user is created for them; they are the one who connects the Tax Authority and can revoke you.
address_street / _house / _city
stringrequired
The business address, printed on every document. After registration there is nobody on our side to ask — the owner has not signed in yet.
phone
string 6–30required
Business phone.
terms_accepted
booleanrequired
Must be true. false returns 422 MAKOR-REG-003 and nothing is created.
owner_name
string ≤200optional
Owner name, used in the invitation.
address_zip
stringoptional
Postal code. Not required — it cannot be derived from an address.
occupation
stringoptional
Line of business.
email
emailoptional
The business's contact address. Falls back to owner_email when omitted.
default_locale
"he" | "en"= "he"
Document language.
email_auto_send
boolean= false
Whether every document the business issues is also emailed to the customer. This is the only moment you decide it — afterwards the switch is the owner's (PATCH /businesses/{id} is owner-only).
What you are asserting, and what it does not do
We did not show the terms to the business owner — you did, and terms_accepted is your attestation, not proof. What we keep is what we actually know: which operator asserted it, through which key, when, and against which terms version — the version and the clock are ours, not yours. Stand behind it. Two things registration does not do: it does not make you the owner — the owner is invited and can disconnect you at any moment — and it does not connect the business to the Tax Authority. That requires the owner to sign in personally, so allocation numbers are unavailable until they do.
GET/operator/businesses

Returns data[] of business_id, legal_name, tax_id, scopes (the effective permission) and granted_at, plus has_more and next_offset. Parameters: limit (default 100, max 500) and offset. This is the only call where an operator key is valid without a business in the path.

A complete integration

Node.js
const KEY = process.env.MAKOR_OPERATOR_KEY;   // mk_op_live_… or mk_op_test_…

const BASE_URL = KEY.startsWith("mk_op_test_")
  ? "https://sandbox.makor.izid.io/api/v1"
  : "https://makor.izid.io/api/v1";

const auth = { Authorization: `Bearer ${KEY}` };

// 1. Ask which businesses have connected you, and what each one allows.
//    Cache this; grant.created / grant.revoked keep it fresh.
const { data } = await (
  await fetch(`${BASE_URL}/operator/businesses`, { headers: auth })
).json();

// 2. From here the API is identical to a single-business integration.
//    The business_id in the path is the only thing that changes.
for (const biz of data) {
  if (!biz.scopes.includes("documents:write")) continue;   // read-only grant

  await fetch(
    `${BASE_URL}/businesses/${biz.business_id}/documents`,
    {
      method: "POST",
      headers: {
        ...auth,
        "Content-Type": "application/json",
        "Idempotency-Key": crypto.randomUUID(),
      },
      body: JSON.stringify({
        doc_type: 320,
        issue_date: new Date().toISOString().slice(0, 10),
        customer_name: "לקוח לדוגמה",
        lines: [
          { description: "מנוי חודשי", quantity: 1, unit_price: 199 },
        ],
        payments: [
          { method: "card", amount: 234.82, card_brand: "Visa" },
        ],
      }),
    }
  );
}
POST/businesses/{business_id}/operators
operator_id
uuidrequired
The operator receiving access. An unknown or inactive operator returns 404.
scopes
string[]required
The ceiling the business grants. Unknown scopes return 422 MAKOR-KEY-001.

Owner-only — an operator can never add itself. Granting twice returns 409 MAKOR-OP-001; re-granting after a revoke reuses the same record, so the history of who was connected stays in one place.

GET/businesses/{business_id}/operators
DELETE/businesses/{business_id}/operators/{grant_id}

Disconnects the operator from this business only. It takes effect on the next request and touches neither the operator’s key nor any of its other businesses.

Connection events

Operator events are registered on the operator’s own endpoints, not a business’s, and are the only events selectable there. Without them you discover a disconnection only when live calls start returning 404.

EventPayload
grant.createdgrant_id, business_id, legal_name, tax_id, scopes
grant.revokedgrant_id, business_id

Webhook endpoints and keys are created in the operator console while signed in, not through the API — a key can never mint another key.

Operator keys are minted only by a signed-in operator admin — a key can never mint another key. key is returned exactly once, prefixed mk_op_live_ or mk_op_test_, so a leaked key announces its blast radius straight off the string.

Tax Authority status#

Whether the business is connected, and therefore whether allocation numbers are available to it at all. An operator that registered a business will want this before trying to issue above the threshold.

GET/ita
GET/exports/unified-format/report

Error code index#

Every business-rule error the API can return. Codes are stable across versions — branch on them.

CodeHTTPMeaning & how to fix
MAKOR-401401Authentication required — missing, unknown or revoked key.
MAKOR-403403Missing scope, or a read-only role attempting a write.
MAKOR-404404Not found, or belongs to another business or environment.
MAKOR-AUTH-001409Email already registered.
MAKOR-AUTH-002401Invalid email or password.
MAKOR-AUTH-003403Account disabled.
MAKOR-BIZ-001422Unknown entity type.
MAKOR-BIZ-002422Tax ID failed check-digit validation.
MAKOR-BIZ-003409User is already a member or already invited.
MAKOR-BIZ-004403The owner membership cannot be revoked.
MAKOR-DOC-000422Unknown document type code.
MAKOR-DOC-003422Give exactly one of doc_type or doc_kind.
MAKOR-DOC-004422Unknown doc_kind — the response lists the known ones.
MAKOR-DOC-013422No amount to infer for payment_method.
MAKOR-DOC-014422Both payment_method and payments were sent.
MAKOR-DOC-015422Unknown payment_method value.
MAKOR-DOC-016422send_email:true on a draft — a draft has nothing to deliver.
MAKOR-DOC-017422Cash above what the Cash Law allows for this transaction price.
MAKOR-DOC-018422Issue date is in the future.
MAKOR-DOC-019422Issue date precedes the previous document in the series.
MAKOR-DOC-041422Customer name missing, and this is not a retail cash sale.
MAKOR-REG-001409You already hold an active grant on that tax ID.
MAKOR-REG-003422terms_accepted must be true.
MAKOR-REG-004409That tax ID is already registered to someone else.
MAKOR-BIZ-005409That tax ID matches more than one business you can reach; address it by business_id.
MAKOR-BIZ-006422An operator key must name a business in the Makor-Business header.
MAKOR-BIZ-007422A signed-in session must name the business explicitly.
MAKOR-DOC-001422This entity type may not issue this document type — e.g. עוסק פטור issuing a tax invoice.
MAKOR-DOC-002422Document has no lines.
MAKOR-DOC-010422Receipt has no payment lines.
MAKOR-DOC-011422Payment lines do not sum to the document total — remember withholding counts as a payment line.
MAKOR-DOC-012422Cheque payment is missing cheque_number.
MAKOR-DOC-020422Credit note is missing parent_id.
MAKOR-DOC-021422Credit notes may only credit a tax invoice or invoice-receipt.
MAKOR-DOC-022422Only receipts may close invoices.
MAKOR-DOC-030422Negative invoice total — issue a credit note instead.
MAKOR-DOC-040422customer_tax_id is required above the allocation threshold.
MAKOR-DOC-050404customer_id does not exist in this business.
MAKOR-DOC-060409Document is not a draft — most often a duplicate issue request.
MAKOR-DOC-061404Document not found.
MAKOR-DOC-062409Cannot finalize from the document's current status.
MAKOR-DOC-063409Drafts are deleted, not cancelled.
MAKOR-DOC-064409Document has linked receipts or credits — credit it instead of cancelling.
MAKOR-DOC-065409Only drafts can be deleted.
MAKOR-LINK-001422Linked invoice not found.
MAKOR-LINK-002422Linked target is not an issued document.
MAKOR-LINK-003422Receipts can only close invoices or proformas.
MAKOR-LINK-004422Link amount must be positive.
MAKOR-LINK-005422Link amount exceeds the invoice's open balance — read open_balance first.
MAKOR-LINK-006422Cannot link documents in different currencies.
MAKOR-CUR-001422Unsupported currency.
MAKOR-CUR-002422An ILS document cannot carry a rate other than 1.
MAKOR-CUR-003422A foreign-currency document needs fx_rate.
MAKOR-CUR-004422fx_rate must be positive.
MAKOR-CUR-005422fx_rate is limited to six decimal places.
MAKOR-CUR-006422The currency has no minor unit — amounts must be whole.
MAKOR-CUR-007422ILS has no exchange rate against itself.
MAKOR-CUR-008503The Bank of Israel rate feed is unavailable — supply fx_rate directly.
MAKOR-CUR-009404No published rate for that date — supply fx_rate directly.
MAKOR-ALLOC-001409Document is not awaiting an allocation decision.
MAKOR-ALLOC-002409The reverse-charge resubmission was also refused.
MAKOR-ALLOC-003409Only an issued document can be allocated after the fact.
MAKOR-ALLOC-004409This document already has an allocation number.
MAKOR-ALLOC-005409The document's allocation state does not allow a new request.
MAKOR-ALLOC-006422This document type cannot carry an allocation number.
MAKOR-ALLOC-007422An allocation number cannot be requested without the customer's tax id.
MAKOR-ALLOC-008409More than a year has passed since the invoice date.
MAKOR-ALLOC-009409This business is not connected to the Tax Authority.
MAKOR-NUM-001422Document type not available for this entity type.
MAKOR-NUM-002409Numbering is locked — documents were already issued in this sequence.
MAKOR-IDEM-001422Idempotency key reused with a different body.
MAKOR-IDEM-002409The original request is still in flight — retry shortly.
MAKOR-PAGE-001422Malformed pagination cursor.
MAKOR-PDF-001409Drafts have no PDF.
MAKOR-SHARE-001409Only issued documents can be shared.
MAKOR-MAIL-001503This deployment has no mail relay configured.
MAKOR-MAIL-002422No address on the customer — supply one in `to`.
MAKOR-MAIL-003422That is not a valid email address.
MAKOR-MAIL-004409Only an issued document can be emailed.
MAKOR-MAIL-005502The relay refused the message — safe to retry.
MAKOR-EXP-001422Invalid export date range.
MAKOR-KEY-001422Unknown scope requested.
MAKOR-WH-001422Unknown webhook event name.
MAKOR-OP-001409The operator already has access to this business — revoke first to change its scopes.
MAKOR-ITA-001409Tax Authority OAuth callback is not applicable in the current mode.

Limits & versioning#

Versioning
/api/v1
The version lives in the path. Additive changes (new fields, endpoints, enum members) ship without a version bump — parse defensively and ignore unknown fields.
Rate limits
none enforced
No hard quota today. Keep concurrency reasonable; issuance is deliberately serialized per numbering sequence, so issuing the same document type in parallel gains you nothing.
Payload size
practical
No fixed cap, but keep documents to a sane number of lines — they must render onto a printable PDF.
Retention
7 years
Issued documents and their original PDFs are retained for the statutory period and cannot be deleted through the API.
Something unclear or missing?
The interactive reference at /api/docs is generated from the live schema and always matches the deployed build — if this page and the schema ever disagree, the schema wins.