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, digitally signed PDFs, and the statutory מבנה אחיד export. Everything is testable in a fully isolated sandbox before you go live.

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

Issue an invoice in one call

Create an API key in הגדרות → API, then post a document. With ?issue=true the draft is created, numbered, cleared with the Tax Authority when required, rendered, signed and issued in a single request.

curl
# Sandbox host + sandbox key. Swap both together to go live.
curl -X POST 'https://sandbox.makor.all-good.co.il/api/v1/businesses/{business_id}/documents?issue=true' \
  -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_agorot": 600000 }
    ]
  }'
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": 600000,
  "discount_total": 0,
  "taxable_amount": 600000,
  "vat_rate_bp": 1800,
  "vat_amount": 108000,
  "total": 708000,
  "withholding_amount": 0,
  "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.all-good.co.il/api/v1"
  : "https://makor.all-good.co.il/api/v1";

const res = await fetch(
  `${BASE_URL}/businesses/${BUSINESS}/documents?issue=true`,
  {
    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_agorot: 19900 }],
      payments: [{ method: "card", amount_agorot: 23482, 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.all-good.co.il/api/v1" if KEY.startswith("mk_test_") else "https://makor.all-good.co.il/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",
        params={"issue": True},
        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_agorot": 600000}
            ],
        },
    )

    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.all-good.co.il/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.all-good.co.il/api/v1. Creates test documents only — see Sandbox.
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
FLOW-401
Missing, malformed, unknown or revoked key.
404 Not Found
FLOW-404
The key is valid but belongs to a different business than the {business_id} in the path. Cross-tenant access is reported as “not found” rather than “forbidden” so key holders cannot probe for the existence of other businesses.
403 Forbidden
FLOW-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
reports:readIncome, VAT and withholding reports
export:readGenerate and download מבנה אחיד exports
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.all-good.co.il/api/v1mk_live_…
Sandboxhttps://sandbox.makor.all-good.co.il/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

הגדרות → סביבת ניסוי → כניסה למצב ניסוי. 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 last two agorot digits of the pre-VAT amount. 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_agorot: 600060 (₪6,000.60) triggers a code 460 hold, while 600000 is approved.

Conventions#

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

Money
integer agorot
All monetary values are integers in agorot (1/100 ₪). ₪1,180.00 is 118000. Never send floats, and never send a VAT amount — VAT is computed server-side.
VAT
server-computed
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 (FLOW-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.all-good.co.il/dev#errors",
  "title": "Payment lines (5000) must sum to document total (11800)",
  "status": 422,
  "code": "FLOW-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 FLOW-* code. Business rules always use FLOW-*.

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 FLOW-IDEM-001 — a key may describe only one request
Same key while the first request is still running409 FLOW-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 FLOW-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 (FLOW-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; Makor retries retroactively (allowed for up to one year).
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.all-good.co.il/api/v1/businesses/{business_id}/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 agorot; monetary fields are always present, even when zero.

id
string (uuid)required
Stable identifier.
doc_type
integerrequired
Type code — see document types. These are the same codes the מבנה אחיד standard uses.
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
integerrequired
Sum of line totals before any document-level discount, excluding VAT.
discount_total
integerrequired
Document-level discount.
taxable_amount
integerrequired
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
integerrequired
Computed VAT.
total
integerrequired
Grand total including VAT.
withholding_amount
integerrequired
Sum of payment lines whose method is withholding (ניכוי מס במקור).
allocation_status
stringrequired
See allocation statuses.
allocation_number
string | nullrequired
Full 26-digit confirmation number. Print the last nine digits.
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
integer | 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

CodeHebrewEnglishNotes
10הצעת מחירQuoteNo bookkeeping effect
100הזמנהOrderNo bookkeeping effect
200תעודת משלוחDelivery note
300חשבונית עסקהProforma invoiceBills without triggering VAT — the cash-basis pattern
305חשבונית מסTax invoiceAllocation number above threshold
320חשבונית מס/קבלהInvoice-receiptRequires payments; allocation applies
330חשבונית זיכויCredit noteRequires parent_id; never cleared
400קבלהReceiptRequires payments; closes invoices via linked_invoices
405קבלה על תרומה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 (FLOW-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; retroactive retry queued

Payment methods

ValueExtra fields
cash
chequeRequires cheque_number; bank_code, branch, account, paid_date recommended
cardcard_brand, card_last4, 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/businesses/{business_id}/documentsdocuments:write

Creates a draft, and with ?issue=true 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.

Query & headers

issue
boolean= false
Issue immediately instead of leaving a draft.
sandbox
boolean= false
Create a test document. Ignored for API keys — the key's own environment always wins.
Idempotency-Key
headeroptional
Strongly recommended whenever issue=true.

Body

doc_type
integerrequired
One of the document type codes.
issue_date
daterequired
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 (FLOW-DOC-040).
lines[]
arrayoptional
description (required, ≤500), quantity (required, > 0), unit_price_agorot (required, ≥ 0), unit, discount_agorot, vat_treatment, item_id. Required for every type except pure receipts.
payments[]
arrayoptional
method and amount_agorot required. Mandatory for 320, 400 and 405, and must sum exactly to the document total (FLOW-DOC-011).
linked_invoices[]
arrayoptional
{ invoice_id, amount_agorot } — 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_agorot
integer= 0
Discount on the whole document, allocated proportionally across the VAT base.
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_agorot": 112100, "paid_date": "2026-08-07" },
    { "method": "withholding",   "amount_agorot": 5900 }
  ],
  "linked_invoices": [
    { "invoice_id": "019fdac1-...", "amount_agorot": 118000 }
  ]
}
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/businesses/{business_id}/documents/{document_id}/issuedocuments:write

Issues an existing draft — same pipeline and same 201/202 semantics as creating with ?issue=true. Issuing anything that is no longer a draft returns 409 FLOW-DOC-060, which is also what a duplicate request looks like.

POST/businesses/{business_id}/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 FLOW-ALLOC-001. If a reverse-charge resubmission is itself refused you get 409 FLOW-ALLOC-002.

GET/businesses/{business_id}/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/businesses/{business_id}/documents/{document_id}documents:read

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

POST/businesses/{business_id}/documents/{document_id}/canceldocuments:write
reason
string ≤500optional
Recorded on the document and 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 FLOW-DOC-064); issue a credit note instead. If the original already reached the customer, a credit note is the legally correct instrument regardless.
DELETE/businesses/{business_id}/documents/{document_id}documents:write

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

GET/businesses/{business_id}/documents/{document_id}/pdfdocuments:read

Returns application/pdf. The first call renders and digitally signs the original (מקור) and stores it; every later call returns those exact bytes, because the signed file is the legal original. Add ?copy=true for a copy (העתק) rendering. Drafts have no PDF (409 FLOW-PDF-001).

POST/businesses/{business_id}/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 FLOW-SHARE-001).

Customers#

GET/businesses/{business_id}/customerscustomers:read

Query & headers

q
stringoptional
Case-insensitive search across name, tax ID and email.
limit
integer= 50
Maximum 200.
POST/businesses/{business_id}/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/businesses/{business_id}/customers/{customer_id}customers:read
PATCH/businesses/{business_id}/customers/{customer_id}customers:write

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

DELETE/businesses/{business_id}/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/businesses/{business_id}/itemsitems:read

Query & headers

q
stringoptional
Name search.
limit
integer= 100
Maximum 500.
POST/businesses/{business_id}/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_agorot
integer= 0
Default price.
vat_treatment
string= "standard"
standard · exempt · zero.
description
string ≤500optional
Long description.
PATCH/businesses/{business_id}/items/{item_id}items:write
DELETE/businesses/{business_id}/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/businesses/{business_id}/numbering

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

PUT/businesses/{business_id}/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 FLOW-NUM-002), because changing it retroactively would break the legally required continuity.

Reports#

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

GET/businesses/{business_id}/reports/incomereports:read

Query & headers

from_date
daterequired
Inclusive.
to_date
daterequired
Inclusive.

Returns monthly[] (month, total_agorot, vat_agorot), by_type[] and total_agorot.

GET/businesses/{business_id}/reports/vatreports:read

Output VAT per month: periods[] with taxable_agorot and output_vat_agorot, plus total_output_vat_agorot.

GET/businesses/{business_id}/reports/withholdingreports:read

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

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

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

POST/businesses/{business_id}/exports/unified-formatexport:read
from_date
daterequired
Inclusive.
to_date
daterequired
Inclusive; must not precede from_date (FLOW-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/businesses/{business_id}/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/businesses/{business_id}/webhooks
url
string (https)required
Destination endpoint.
events
string[]required
At least one event name; unknown names are rejected with 422 FLOW-WH-001.

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

EventEmittedPayload
document.issuedYesdocument_id, doc_type, doc_number, total_agorot, allocation_status
allocation.rejectedYesdocument_id, rejection_code
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_agorot": 708000,
    "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/businesses/{business_id}/webhooks
GET/businesses/{business_id}/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/businesses/{business_id}/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/businesses/{business_id}/api-keys
name
string ≤100required
Label shown in the dashboard.
scopes
string[]optional
Defaults to the maximum your role allows. Unknown scopes return 422 FLOW-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/businesses/{business_id}/api-keys

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

DELETE/businesses/{business_id}/api-keys/{key_id}

Revokes immediately. Owner or employee role required.

Error code index#

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

CodeHTTPMeaning & how to fix
FLOW-401401Authentication required — missing, unknown or revoked key.
FLOW-403403Missing scope, or a read-only role attempting a write.
FLOW-404404Not found, or belongs to another business or environment.
FLOW-AUTH-001409Email already registered.
FLOW-AUTH-002401Invalid email or password.
FLOW-AUTH-003403Account disabled.
FLOW-BIZ-001422Unknown entity type.
FLOW-BIZ-002422Tax ID failed check-digit validation.
FLOW-BIZ-003409User is already a member or already invited.
FLOW-BIZ-004403The owner membership cannot be revoked.
FLOW-DOC-000422Unknown document type code.
FLOW-DOC-001422This entity type may not issue this document type — e.g. עוסק פטור issuing a tax invoice.
FLOW-DOC-002422Document has no lines.
FLOW-DOC-010422Receipt has no payment lines.
FLOW-DOC-011422Payment lines do not sum to the document total — remember withholding counts as a payment line.
FLOW-DOC-012422Cheque payment is missing cheque_number.
FLOW-DOC-020422Credit note is missing parent_id.
FLOW-DOC-021422Credit notes may only credit a tax invoice or invoice-receipt.
FLOW-DOC-022422Only receipts may close invoices.
FLOW-DOC-030422Negative invoice total — issue a credit note instead.
FLOW-DOC-040422customer_tax_id is required above the allocation threshold.
FLOW-DOC-050404customer_id does not exist in this business.
FLOW-DOC-060409Document is not a draft — most often a duplicate issue request.
FLOW-DOC-061404Document not found.
FLOW-DOC-062409Cannot finalize from the document's current status.
FLOW-DOC-063409Drafts are deleted, not cancelled.
FLOW-DOC-064409Document has linked receipts or credits — credit it instead of cancelling.
FLOW-DOC-065409Only drafts can be deleted.
FLOW-LINK-001422Linked invoice not found.
FLOW-LINK-002422Linked target is not an issued document.
FLOW-LINK-003422Receipts can only close invoices or proformas.
FLOW-LINK-004422Link amount must be positive.
FLOW-LINK-005422Link amount exceeds the invoice's open balance — read open_balance first.
FLOW-ALLOC-001409Document is not awaiting an allocation decision.
FLOW-ALLOC-002409The reverse-charge resubmission was also refused.
FLOW-NUM-001422Document type not available for this entity type.
FLOW-NUM-002409Numbering is locked — documents were already issued in this sequence.
FLOW-IDEM-001422Idempotency key reused with a different body.
FLOW-IDEM-002409The original request is still in flight — retry shortly.
FLOW-PAGE-001422Malformed pagination cursor.
FLOW-PDF-001409Drafts have no PDF.
FLOW-SHARE-001409Only issued documents can be shared.
FLOW-EXP-001422Invalid export date range.
FLOW-KEY-001422Unknown scope requested.
FLOW-WH-001422Unknown webhook event name.
FLOW-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 signed originals 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.