Documents
5.1 Parties (customers): optional but recommended
A document may name an ad-hoc party inline (just {"name": "..."}), which is fine for walk-ins. For repeat customers create a party once and reference it, so the customer ledger stays coherent:
curl -X POST "$BASE/businesses/$BID/parties" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
-d '{ "kind": "customer", "name": "דנה לוי", "phone": "0521111111", "email": "dana@example.com" }'
# → {"ok":true,"party":{"id":"...","key":"C00001",...}}
# list / update / record consent to digital documents:
curl -H "X-Api-Key: $KEY" "$BASE/businesses/$BID/parties?kind=customer"
curl -X PATCH "$BASE/businesses/$BID/parties/$PARTY_ID" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{"phone":"0529999999"}'
curl -X POST "$BASE/businesses/$BID/parties/$PARTY_ID/consent" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{"via":"checkout-checkbox"}'
kind: "customer" | "supplier" | "both". The consent endpoint timestamps the party's agreement to receive documents digitally (מסמכים ממוחשבים). Record it once per customer if you deliver PDFs instead of paper.
5.2 Create a draft: POST /businesses/:bid/documents
curl -X POST "$BASE/businesses/$BID/documents" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: order-8812" -d '{
"docType": 320,
"party": { "name": "דנה לוי", "phone": "0521111111" },
"priceMode": "gross",
"lines": [
{ "description": "איפור כלה", "quantity": 1, "unitPriceExVat": 120000 },
{ "description": "איפור מלווה", "quantity": 2, "unitPriceExVat": 35000 }
],
"payments": [ { "method": 3, "amount": 190000 } ]
}'
# 201 → {"ok":true,"document":{"id":"...","status":"draft","docNumber":null,...}}
| Field | Notes |
|---|---|
docType | 300 / 305 / 320 / 400 (330 only via the credit-note endpoint) |
party | {partyId} to reference a saved party, or inline {name, taxId?, phone?, email?, address?}; omit entirely for an anonymous cash receipt |
lines | Required for 300/305/320: {description, quantity, unitPriceExVat}. Optional per line: sku, unit (default "יחידה"), lineDiscount (agorot), vatRateBp: 0 to make that one line VAT-exempt on an otherwise VAT-bearing document (mixed rates: VAT is then computed per line and summed, the totals carry vatRateBp: null and vatByRate, and a document-level discountAmount is refused; use line discounts) |
priceMode | "net" (the default: line prices are ex-VAT) or "gross": the same unitPriceExVat/lineDiscount fields carry VAT-inclusive amounts, the document total anchors to the gross sum and VAT is derived. Gross is what a consumer-facing price list wants: in the example above the customer pays exactly ₪1,900.00 |
payments | Required for 320/400: {method, amount} + optional {dueDate, bankCode, branchCode, accountNumber, checkNumber, cardCompany, cardName, cardTxType} |
note | Free text (≤500 chars) printed on the document under the tables: remark, service context, terms. A credit note automatically carries its reason here |
documentDate, valueDate | Default today (Asia/Jerusalem) |
discountAmount, withholdingAmount | Document-level, agorot. Payments must cover the total net of withholding |
currency | ILS (default) or USD / EUR / GBP / CHF / AUD / CAD. A foreign currency makes every amount in the body (lines, discounts, payments) that currency's minor unit, cents instead of agorot. See "Foreign currency" below |
fxRate | ILS per 1 unit of currency, your own rate. Omit to use the Bank of Israel שער יציג of the document date, fetched at issue. fxRateDate says which day yours is from |
zeroVat | true states מע"מ בשיעור אפס (סעיף 30(א) לחוק מע"מ: a service to a foreign resident, export) on a 305/320: lines carry 0% VAT and the document prints the legal basis. Your claim, not checked by BillOS. Ignored on a receipt or for a עוסק פטור |
lang | he (default) or en: an English, left-to-right document for a foreign customer. Labels, the headline sentence, dates (September 4, 2026) and the email that carries the link are English. The document is bilingual by design, not English-only: the statutory headings (the document type, עוסק מורשה / ח.פ / עוסק פטור, מקור/העתק, מסמך ממוחשב, the signature line) stay in Hebrew beside their English, because an English-only original requires a written permit from the assessing officer and a bilingual one does not. Your line descriptions and note print as you send them, so write those in English too |
externalRef | Idempotency (or the Idempotency-Key header) |
Payment method accepts a code or a string alias:
| Code | Alias | Label on the PDF |
|---|---|---|
1 | "cash" | מזומן |
2 | "check" / "cheque" | המחאה |
3 | "card" / "credit" | כרטיס אשראי |
4 | "transfer" / "bank_transfer" | העברה בנקאית |
5 | "voucher" | תווי קניה |
8 | "standing_order" | הוראת קבע |
9 | "other" | אחר |
91 | "bit" | ביט |
92 | "paybox" | פייבוקס |
93 | "payment_app" / "app" | אפליקציית תשלום |
Codes 91-93 are BillOS extensions so the printed document names the actual payment app; in the regulatory OPENFRMT export they are reported as 9 (אחר) per the ITA spec. Unknown values fall back to 9.
Foreign currency (מט"ח)
Israeli books are kept in shekels: סעיף 22 להוראות ניהול פנקסי חשבונות requires the record in Israeli currency and, when the payment came in another one, the currency and the amount paid in it beside the shekel figure. That is exactly what a foreign-currency document is in BillOS:
- You send the document in its own currency (
currency: "USD", amounts in cents). The customer's PDF shows dollars throughout, then the rate and the shekel total under the totals: "שער יציג USD (04/09/2026) 3.0050 · סה״כ בש״ח ₪300.50". - At issue the ledger converts at
fxRate(yours) or the Bank of Israel שער יציג of the document date (the last rate published on or before it), and stores shekels intotalIncVat/vatAmount/totalExVatand on every line, the way it always has. The document's own figure stays infxTotalIncVat, withfxRateandfxRateDatefrozen on the row and in the snapshot. Rounding is anchored on the total (the same מע"מ כלול reasoning aspriceMode: "gross"), so the exported records still reconcile. - The מבנה אחיד export fills the C100 fields 1217/1218 (סכום סופי במט"ח, קוד מט"ח) and the B100 currency fields, so the accountant's software sees both figures.
- A credit note against a foreign-currency document is denominated the same way at the same rate, so it reverses the exact shekel amounts; a reversal at the day's rate would leave an exchange difference nobody asked for.
GET /businesses/:bid/fx-rate?currency=USD&date=2026-09-04returns the rate the issue would use, so a form can show it first. Passing your ownfxRate(the bank's actual conversion, a contract rate) is always allowed; if no rate can be had and you passed none, the issue answers422 fx_rate_requiredrather than guessing.- Pair it with
lang: "en"for a document the customer can read: the same record, an English LTR page. - VAT is unchanged by currency. A foreign customer does not by itself mean 0% VAT: the zero rate of סעיף 30 has conditions (and excludes, for instance, a service about an asset located in Israel). When they hold, send
zeroVat: trueand the document says so.
curl -X POST "$BASE/businesses/$BID/documents" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{
"docType": 320, "currency": "USD", "lang": "en", "zeroVat": true,
"party": { "name": "Acme Inc.", "address": "Austin, TX" },
"lines": [ { "description": "Consulting, September", "quantity": 10, "unitPriceExVat": 15000 } ],
"payments": [ { "method": 4, "amount": 150000 } ]
}'
# → a $1,500.00 document; at issue: fxRate 3.005 (BoI, 2026-09-04), totalIncVat 450750 agorot, fxTotalIncVat 150000
Drafts are freely mutable: they are a "קובץ זמני", not a tax document; any rendering of a draft is watermarked "טיוטה" and carries no number:
curl -X PATCH "$BASE/businesses/$BID/documents/$DOC_ID" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{"lines":[...]}'
curl -X DELETE "$BASE/businesses/$BID/documents/$DOC_ID" -H "X-Api-Key: $KEY"
5.3 List / read
curl -H "X-Api-Key: $KEY" "$BASE/businesses/$BID/documents?status=issued&docType=320&from=2026-07-01&to=2026-07-31"
curl -H "X-Api-Key: $KEY" "$BASE/businesses/$BID/documents/$DOC_ID"
5.4 Open items: a receipt names the invoices it pays
A tax invoice (305) or proforma (300) is a debt until something pays it. That something is a receipt (400) that names it:
curl -X POST "$BASE/businesses/$BID/documents" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{
"docType": 400, "party": {"partyId": "'$PARTY'"},
"payments": [{"method": "transfer", "amount": 118000}],
"settles": [{"documentId": "'$INVOICE_ID'", "amount": 118000}]
}'
settles is a list of issued 305/300 documents of the same customer, each with an optional agorot amount; without one the receipt covers as much of that document's balance as it can, in order. It is checked when the draft is created and again at issue (the balance may have moved), the receipt prints "על חשבון: חשבונית מס 1234 …" (הוראה 5: the receipt states the account it credits), and the invoice's balance falls by that amount. A credit note based on an invoice reduces its balance too; a negative receipt that reverses a receipt un-pays what it paid.
What you read back:
GET /documents/:idon a 305/300 carriessettlement: { status, total, settledAmount, creditedAmount, balance, settledBy: [...] };statusisopen,partial,paidorcancelled. On a receipt it carriessettles: [...]. List rows of a 305/300 carrybalanceandpaymentStatus.GET /businesses/:bid/receivables?partyId=&asOf=is the open-items report: every document still owed,daysOpen,daysOverdue(when the invoice states adueDate), the four ageing buckets (current0-30,d31_60,d61_90,d90plus) andtotalOpen/overdue.- The moment a document's balance reaches zero, a
document.paidevent fires (section 11): the trigger for "mark the order paid" automations.
Money received without knowing which invoice it is for: issue the receipt with no settles, and match later with POST /documents/:id/settlements {"documentId", "amount?"} on the receipt (removable with DELETE .../settlements/:sid; an allocation printed on the receipt is not). A dueDate on a 305/300 prints "לתשלום עד" and anchors its ageing.
There is no law that ties a receipt number to an invoice number, and BillOS does not force one: a receipt with no settles is a valid receipt. What the practice settled on, here and in every Israeli invoicing product, is that the receipt says what it is for and the ledger keeps the balance.
5.5 Preview a draft: POST /businesses/:bid/documents/:id/preview
Returns the draft rendered as a "טיוטה"-watermarked PDF (raw application/pdf bytes) so your user can see exactly how the document will look before the irreversible issue. Nothing is recorded and no number is consumed. Optional body {"branding": {...}} overrides the stored branding for this render. Drafts only: an issued document's PDFs are frozen; download those via prints (409 not_draft otherwise).
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/preview" -H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" -d '{}' -o preview.pdf
5.6 Issue: POST /businesses/:bid/documents/:id/issue
The point of no return. Assigns the next gapless document number, freezes a full snapshot, renders the PDF and signs it digitally.
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/issue" -H "X-Api-Key: $KEY"
# 201 → {"ok":true,"document":{"docNumber":1071,"status":"issued","totalIncVat":190000,"vatAmount":28983,...}}
- Totals are recomputed server-side at issue from the frozen lines, so what you get back is authoritative.
- Issuing twice is safe: the second call returns the same document with
"deduped": true. - Optional body
{"branding": {...}}overrides the business's stored branding for this document only (same fields as the branding section above); omit it to use the stored defaults. - If PDF rendering hiccups, the document is still legally issued (numbering never rolls back); the PDF is rendered by a background retry, and
printsreturnspdf_pendinguntil it's ready.
Check first: POST /businesses/:bid/documents/:id/validate. A dry run of issue. It runs every check issuing runs (account standing, the dealer type, totals, payments against the total, the allocation rule, the exchange rate, what a receipt settles) and stops before the first write: no number is taken, nothing is signed, the draft is unchanged.
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/validate" -H "X-Api-Key: $KEY"
# 200 → {"ok":true,"valid":true,"wouldIssue":{"docType":320,"currency":"ILS","totalExVat":161017,"vatAmount":28983,"totalIncVat":190000,"allocation":"not_required"},"warnings":[]}
# or the exact refusal issue would give: {"error":"payments 30000 != total 35400","reason":"payments_mismatch","hint":"…","param":"payments",…}
Use it whenever a mistake would be expensive: an issued document is corrected only by a credit note. It needs documents:read, since it changes nothing.
5.7 Correct: POST /businesses/:bid/documents/:id/credit-note
The only way to "cancel" an issued 305/320. Issues a linked חשבונית מס זיכוי (330); for a receipt (400) it issues a negative receipt. The credit prints "בגין חשבונית מס 1234 (01/09/2026)" and your reason, which is what הוראה 23א asks a credit note to state: the invoice it refers to, the reason, the amount.
# In full: mirrors the original, the source is marked cancelled
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/credit-note" -H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" -d '{"reason":"הזמנה בוטלה"}'
# In part, by amount (VAT included, agorot): the source stays open on the remainder
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/credit-note" -H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" -d '{"reason":"הנחה שסוכמה בדיעבד","amount":23600}'
# In part, by lines: the credited items, as on the original
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/credit-note" -H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"פריט הוחזר","lines":[{"description":"מנורת שולחן","quantity":1,"unitPriceExVat":20000}]}'
# 201 → { document: the credit note, source: { cancelledById, creditedAmount, remaining } }
A partial credit requires a reason, and the credits against one document can never add up to more than its total (הוראה 23א(2)(ב)); the moment they do, the source is marked cancelled like a full credit would. A 320 (חשבונית מס-קבלה) that gave money back can issue the negative receipt for the refund in the same call ("refund": true, or {"method": "card"}), the pair a partial credit of an invoice-receipt needs. A receipt is corrected by amount (amount = the part refunded); if it had paid invoices, that much is un-paid.
Send an Idempotency-Key (your refund id). A credit note is as permanent as the document it corrects: with the key, a call sent twice answers the credit note already issued with "deduped": true instead of a second one, and a key that already produced a different document is 409 conflict.
A חשבון עסקה (300) has no credit note. It is not a tax document, so a wrong one is voided: POST /businesses/:bid/documents/:id/void with an optional {"reason": "..."}. The document, its PDFs and its place in the exports stay; it reads as cancelled and stops being owed. It is refused once a receipt was settled against it (cancel the receipt first) and on any other type.
What the law says, in short: an invoice that has left your hands is never edited (הוראה 23); a transaction cancelled in whole or in part, a changed amount or an error is corrected by a הודעת זיכוי that names the invoice, the reason and the amount (23א); and reducing your VAT liability by it has its own conditions (the customer acknowledged it, or it went by registered mail) that are yours to meet, not the software's.
5.8 Get the PDF: POST /businesses/:bid/documents/:id/prints
Returns raw application/pdf bytes. kind is one of:
"auto": recommended. The origin (מקור) on the first ever print, a copy (העתק) on every print after; never errors on a repeat request. TheX-Print-Variantresponse header (origin|copy) says which one you got, so deliver anoriginstraight to the end customer."origin": explicitly the מקור. Succeeds exactly once per document; a second call returns400 {"error":"origin_already_printed"}."copy"(default): a העתק-watermarked copy, any number of times."view": the same העתק for showing on screen. A copy is counted and audited each time it is handed out; aviewis neither, so use it for a document opened in your own interface andcopyfor a download.
# a "download PDF" button can always just send auto:
curl -X POST "$BASE/businesses/$BID/documents/$DOC_ID/prints" -H "X-Api-Key: $KEY" \
-H "Content-Type: application/json" -d '{"kind":"auto"}' -o invoice.pdf
409 {"reason":"pdf_pending"} → the PDF isn't rendered yet (rare, right after issue); retry with backoff; the origin is never consumed by a pdf_pending response. PDFs are PAdES-signed with a per-business certificate chained to the BillOS CA. Fetch the CA certificate at GET https://api.billos.co.il/ca.pem to verify signatures.
5.9 Allocation numbers (מספר הקצאה, חשבוניות ישראל)
Under the חשבוניות ישראל reform, a tax invoice (305/320) needs an allocation number from the Tax Authority when all of these hold: the amount before VAT exceeds the statutory threshold (₪5,000 since June 2026), the invoice carries VAT, and the buyer is an identified business, which needs the number to deduct input VAT. Credit notes (330) don't require one.
For a business connected to the Tax Authority, BillOS requests the number itself, inside the issue call: it comes back on the document as allocationNumber, prints on it under "מספר הקצאה", and is announced by document.allocation_assigned. Connecting is the owner's one-time consent (below). For a business that is not connected, or when the Authority refuses or does not answer, the document issues with a warning: allocation_missing when nothing was asked, allocation_not_approved, allocation_pending_decision or allocation_decision_sent when the Authority answered with its decision code (460, 461, 462), allocation_failed when it could not be reached; if the business opts into strict mode (PATCH /businesses/:bid {"requireAllocation": true}), issue returns 422 allocation_required instead and nothing is numbered. A number obtained elsewhere can also be supplied as allocationNumber on the draft. Most consumer-facing flows (B2C, receipts) never hit the threshold.
5.9.1 Connecting a business to the Tax Authority: POST /businesses/:bid/ita/connect
The Tax Authority grants the right to request numbers to a business, not to a software house, so the business's owner consents himself, once, on the Authority's own page with his own credentials. Your app starts it and gets him back:
POST /v1/businesses/<bid>/ita/connect
{ "returnUrl": "https://app.example.com/settings/tax", "state": "user-1842" }
→ 201 { "ok": true, "url": "https://openapi.taxes.gov.il/…", "expiresAt": "…", "sandbox": false }
- Send the owner to
url: open it in his browser, or pass"link": truefor an address you send him by email or WhatsApp (good for 24 hours instead of 15 minutes). - He signs in at רשות המסים and approves BillOS.
- His browser lands on your
returnUrlwithita=connected(ordenied, orerrorwith areason),business=<bid>and yourstate. - Confirm with
GET /businesses/:bid/itaor theita.connectedwebhook before you tell him it worked: a query string is something anyone can type.
returnUrl is not registered ahead. It must be https (http to localhost is accepted in the sandbox), at most 512 characters, with no credentials, no fragment, and none of the four parameters BillOS adds. state is yours: printable ASCII, up to 128 characters, echoed back unchanged. Only the business's owner (the key that created it) may start or remove a connection; anyone the business is open to may read it. A business that is pending, suspended or paused cannot be connected.
What BillOS holds is the authorization, encrypted, and it never leaves BillOS: you read a status, never a token.
{ "ok": true, "ita": {
"status": "active", "connected": true,
"connectedAt": "2026-10-27T09:12:00.000Z", "expiresAt": "2027-01-25T09:12:00.000Z", "daysLeft": 90,
"configured": true, "sandbox": false } }
status is not_connected, active, expiring or expired. A consent lasts 90 days, and refreshing does not extend it: only a new consent does. From 7 days before the end the status reads expiring (still connected) and ita.expiring fires once; ask the owner to connect again then, through the same call, and the new consent replaces the old one. At the end it reads expired, ita.expired fires, and documents that need a number issue without one until he does. DELETE /businesses/:bid/ita removes the authorization (ita.disconnected).
reason on an error return: exchange_failed (the Authority refused the code), authority_unreachable (it did not answer in time after the owner approved: start again), authority_error, or wrong_mode. On denied it is owner_declined. A url past its expiresAt opens a BillOS page that says the link is no longer valid and does not return to you.
In the sandbox (a bk_test_ key) the same call returns a BillOS page that stands in for the Authority, with אישור and סירוב buttons; nothing reaches רשות המסים and no ITA credentials are involved. POST /businesses/:bid/ita/simulate {"state":"expiring"} (or "expired") moves a connected sandbox business to the end of its consent, so ita.expiring, ita.expired and your reminders can be tried today. A live key is refused (403 forbidden).
5.10 Recurring documents: POST /businesses/:bid/recurring
A plan is a template, not a document: the same fields POST /documents takes, plus a schedule. Every run (once a day, early morning Israel time) creates an ordinary draft with that day's date, or issues it when autoIssue: true, filed under externalRef: recurring:<planId>:<runDate> so a run can never double up, and writes a recurring.ran event (outcome: draft | issued | failed) that is the trigger for "email it" or "alert me" automations.
curl -X POST "$BASE/businesses/$BID/recurring" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{
"docType": 305, "partyId": "'$PARTY'", "interval": "monthly", "dayOfMonth": 1,
"lines": [ { "description": "מנוי חודשי", "quantity": 1, "unitPriceExVat": 50000 } ],
"payments": [ { "method": "transfer", "amount": 59000, "dueDate": "2026-10-15" } ],
"note": "לתשלום תוך 14 יום",
"autoIssue": false
}'
# 201 → {"ok":true,"plan":{"id":"…","nextRunOn":"2026-10-01","active":true,…}}
interval:monthly|quarterly|yearly, ondayOfMonth1..28 (so every month has it).nextRunOndefaults to that day this month if still ahead, else next month;endOnpauses the plan after its last run.autoIssue: false(the default) leaves a draft to review. A tax document gets a permanent number when issued, so watch a few drafts before switching a plan toautoIssue: true; a subscription business then gets its invoices without a hand on them.GET/PATCH/DELETE /businesses/:bid/recurring/:id,GET /businesses/:bid/recurring?active=;POST /businesses/:bid/recurring/:id/runproduces today's document now without moving the schedule. Deleting a plan never touches the documents it produced.- A plan in a foreign currency uses the Bank of Israel rate of each run's date unless it carries a fixed
fxRate(a contract at an agreed rate). Payments for a 320/400 plan must cover the total exactly as on a one-off document, or the run fails and says so.
5.11 Moving to BillOS: import the history
A business that switches to BillOS brings two things from its previous software: a numbering to continue, and seven years of documents it must still be able to produce. The first is startNumbers on POST /businesses (section 4): {"305": 1071} makes the first tax invoice here #1071, so the series the previous system left off is continued rather than restarted. The second is the archive:
curl -X POST "$BASE/businesses/$BID/legacy-documents" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" \
-d '{"source":"morning","docType":320,"docNumber":"10042","issuedOn":"2025-03-14","partyName":"דנה ישראלי","totalIncVat":35400,"vatAmount":5400}'
# 201 → {"ok":true,"document":{"id":"…","source":"morning","docNumber":"10042",…,"file":null},"deduped":false}
curl -X PUT "$BASE/businesses/$BID/legacy-documents/$ID/file" -H "X-Api-Key: $KEY" -H "Content-Type: application/pdf" --data-binary @10042.pdf
curl "$BASE/businesses/$BID/legacy-documents?from=2025-01-01&to=2025-12-31&q=דנה" -H "X-Api-Key: $KEY"
curl "$BASE/businesses/$BID/legacy-documents/$ID/file" -H "X-Api-Key: $KEY" -o 10042.pdf
What an archived document is, and is not:
- Metadata as the previous system printed it (
source, theirdocNumber,issuedOn,partyName,totalIncVatin minor units, negative for a refund) plus the original file (PDF, JPEG or PNG, up to 8MB, as raw bytes with its ownContent-Type). Name the kind withdocType(an ITA code) when you know it, ordocKind(their label) when you do not. - Never a BillOS document. It takes no number from any series, is never signed or re-rendered, and never enters the מבנה אחיד, the VAT return, the reports or a credit note. It was issued by another registered software under its own numbering; a second tax document for the same sale is exactly what an import must not create. Correcting one is the previous system's business, or a new BillOS document.
- Re-runnable.
source+docNumberis the key under a business: posting the same one again updates the row and answers200withdeduped: true, so an import that stopped halfway is simply run again. A file uploaded again replaces the earlier one. - Kept with the ledger. The file is a ledger file like every issued PDF: same bucket, same backup. The console lists the archive under מסמכים → ארכיון, with search and download.
Sources people arrive from, as slugs: morning, icount, greeninvoice, ezcount, rivhit, sumit, hashavshevet, priority; any other a-z0-9_- slug is accepted.
5.12 Quotes (הצעת מחיר): POST /businesses/:bid/quotes
A quote is the offer BEFORE the sale, and it is deliberately not a document. It has no statutory number, it is freely editable and deletable, and it appears in no מבנה אחיד, no PCN874, no income report and no receivables figure. Israeli document law does not know it: נספח 1 has no code for it (100 is הזמנה, the customer's *order*, a different act), so giving it a docType would put a freely-edited row inside the permanent file and force every reader that sums money to remember to exclude it.
What it shares with a document is the machinery worth sharing: the same customer master, the same totals arithmetic (so the invoice repeats the offer to the agora), the same foreign-currency rules and the same two PDF designs. What it never wears is a tax document's furniture: no מקור/העתק, no "מסמך ממוחשב", no signature stamp, no allocation line. Each of those is a legal claim, and on an offer each would be a false one.
curl -X POST "$BASE/businesses/$BID/quotes" -H "X-Api-Key: $KEY" -H "Content-Type: application/json" -d '{
"title": "טיפול שנתי במשרדים",
"party": { "name": "מלון הצוק", "phone": "0501112223", "email": "office@example.com" },
"priceMode": "gross",
"validUntil": "2026-10-15",
"terms": "המחיר כולל שני ביקורי מעקב. תשלום שוטף + 30.",
"lines": [ { "description": "הדברה יסודית", "quantity": 1, "unitPriceExVat": 118000 } ]
}'
# 201 → {"ok":true,"quote":{"id":"…","state":"draft","number":null,"totalIncVat":118000,…}}
curl -X POST "$BASE/businesses/$BID/quotes/$QID/send" -H "X-Api-Key: $KEY"
# 201 → {"ok":true,"quote":{"number":1001,"state":"sent","publicUrl":"https://api.billos.co.il/q/…"},"pdf":{…}}
sendis what makes it real, and it is the closest a quote comes to issuing. It takes a number from its own ascending series, freezes the offer, renders the PDF and mintspublicUrl, the page where the customer reads the offer and accepts or declines it. Nothing about it is permanent: re-sending keeps the same number and the same link and re-freezes the offer (revisedAt), so a link already sitting in a WhatsApp thread never breaks and never shows a stale price.stateisstatuswith expiry folded in.expiredis derived on read (sent, and pastvalidUntil) and never stored. So extending the validity un-expires a quote, with no scheduler and nothing to backfill.viewedAtis stamped the first time the customer opens the page, which is the most useful thing this object can tell a salesperson.- The customer's answer arrives through the public page (
quote.accepted/quote.declined), or you record one that came by phone withPOST .../answer. Recording is allowed on an EXPIRED quote on purpose: "he called and said yes" is exactly when a lapsed date should not be in the way. convertopens a DRAFT document with the quote's lines, in its own price mode and currency, filed underexternalRef: quote:<id>so two calls return one draft rather than a twin. It stops at the draft deliberately: issuing takes a permanent number and may call the tax authority, and an acceptance landing at 2am must not do that by itself. The quote itself is untouched: it stays exactly as the customer accepted it, which is what he will hold up if the invoice does not match.cancelwithdraws it: the public link stops answering and its token is cleared, because a withdrawn offer whose link still renders it is the customer's evidence that it stood. The row and its history stay, andreopenputs it back.- Editing is open while a quote is a draft or sent, because an offer gets haggled over, and that is what an offer is for. It is refused once the customer has answered: rewriting it would make the record of his answer a lie.
- A foreign-currency quote states its own figure and the shekel equivalent at the Bank of Israel rate of its date. Unlike a document, a quote with no rate to be had still renders: the offer is what matters, and the invoice is where the rate has to be right.