The EMDI web API lets your program talk to EMDI web (contacts, products, calendar events, documents) without opening the screens by hand.
You send a JSON message (structured text) and get a JSON reply. Field names are in English (e.g. name, city).
Before you start
- In the packages admin, enable EMDI web API for the product.
- In EMDI web open Settings → Apps → EMDI web API links.
- Generate a key and save the HTTP Key.
- If you changed the package, log out and log back into EMDI web.
- Keep handy: the username and HTTP Key (do not publish them).
Your first call
Every call uses the same URL:
https://web.sbzsystems.com/apps/api/call.php?user=YOUR_USER&key=YOUR_HTTPKEY
Replace YOUR_USER and YOUR_HTTPKEY with your own credentials.
- Open a terminal (or Postman if you prefer).
- Copy the cURL command below.
- Run it. If everything works, you will see
"success": trueand the result. - If you see an error, read
error.message— it usually says what is missing (wrong key, etc.).
This first call returns the lookup lists:
|
1 2 3 4 5 6 7 8 |
curl -sS -X POST "https://web.sbzsystems.com/apps/api/call.php?user=YOUR_USER&key=YOUR_HTTPKEY" \ -H "Content-Type: application/json; charset=utf-8" \ -d '{ "action": "meta.lookups", "with": [ "cities" ] }' |
Successful response (example):
|
1 2 3 4 5 6 7 8 9 10 11 |
{ "success": true, "id": 123, "data": { "id": 123, "code": "C001", "name": "Γιάννης", "surname": "Παπαδόπουλος", "active": 1 } } |
Failed response (example):
|
1 2 3 4 5 6 7 8 |
{ "success": false, "error": { "code": "validation", "message": "id is required", "field": "id" } } |
Rate limit
You can make up to 60 calls per minute (per user and IP). If you hit the limit, wait a moment and try again.
Action list
Click an action in the table to jump straight to the example below. Each action has a name like contact.get. Always send the full JSON (not only the action). Under each request you also see an example response.
| Method | Action | What it does |
|---|---|---|
| POST | meta.lookups |
get lookup lists (cities, VAT, document types…) |
| POST | contact.get |
read one contact |
| POST | contact.list |
list contacts |
| POST | contact.save |
create or update a contact |
| POST | contact.delete |
delete a contact |
| POST | product.get |
read one product |
| POST | product.list |
list products |
| POST | product.save |
create or update a product |
| POST | product.delete |
delete a product |
| POST | calendar.get |
read one calendar event |
| POST | calendar.list |
list calendar events |
| POST | calendar.save |
create or update a calendar event |
| POST | calendar.delete |
delete a calendar event |
| POST | document.get |
read one document |
| POST | document.list |
list documents |
| POST | document.save |
create or update a document |
| POST | document.delete |
delete a document |
Use this first. It returns the lists you need later (e.g. document types, VAT, relations). If you set cities, it also returns cities.
| Field | Meaning |
|---|---|
action |
what you want to do — here always meta.lookups |
with |
optional: set [“cities”] if you also want cities |
|
1 2 3 4 5 6 |
{ "action": "meta.lookups", "with": [ "cities" ] } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
{ "success": true, "data": { "doc_types": [ { "id": 12, "name": "Τιμολόγιο πώλησης", "series": "Α", "kind": { "xrevstiko": 1, "emf_timvn": 1 }, "tax_docs": "", "till": "" } ], "vat_rates": [ { "id": 1, "rate": 24, "title": "24%" } ], "users": [ { "id": 1, "name": "admin" } ], "relations": [ { "id": 1, "name": "Πελάτης" } ], "units": [ { "id": 1, "name": "τεμ" } ], "main_categories": [ { "id": 1, "name": "Γενικά" } ], "categories": [ { "id": 1, "name": "Λοιπά", "main_category_id": 1 } ], "payment_methods": [ { "id": 1, "name": "Μετρητά" } ], "event_types": [ { "id": 1, "name": "Ραντεβού", "color": 0 } ], "cities": [ { "id": 1, "name": "Αθήνα", "country": "GR" } ] } } |
Reads one contact by its internal id (id). Replace 123 with a real id from your database.
| Field | Meaning |
|---|---|
action |
contact.get |
id |
the contact id (Aa) |
|
1 2 3 4 |
{ "action": "contact.get", "id": 123 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
{ "success": true, "id": 123, "data": { "id": 123, "code": "C001", "name": "Γιάννης", "surname": "Παπαδόπουλος", "company": "Παράδειγμα ΑΕ", "phone": "2100000000", "mobile": "6900000000", "fax": "", "email": "john@example.com", "address": "1 Main St", "area": "Center", "postal_code": "10431", "vat_number": "999999999", "notes": "Δημιουργήθηκε από EMDI web API", "city": "Αθήνα", "tax_office": "Αθηνών", "occupation": "Υπηρεσίες", "relation": "Πελάτης", "payment_method": "Μετρητά", "user": "admin", "parent_contact_id": null, "active": 1, "balance": 0, "created": "2026-09-01", "modified": "2026-09-15T12:00:00" } } |
Returns many contacts. limit sets how many (max 200). offset sets where to start (0 = from the beginning).
| Field | Meaning |
|---|---|
action |
contact.list |
active |
1 = active only, 0 = inactive |
limit |
how many rows to return |
offset |
how many to skip from the start |
|
1 2 3 4 5 6 |
{ "action": "contact.list", "active": 1, "limit": 50, "offset": 0 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "success": true, "data": { "items": [ { "id": 123, "code": "C001", "name": "Γιάννης", "surname": "Παπαδόπουλος", "company": "Παράδειγμα ΑΕ", "phone": "2100000000", "mobile": "6900000000", "fax": "", "email": "john@example.com", "address": "1 Main St", "area": "Center", "postal_code": "10431", "vat_number": "999999999", "notes": "Δημιουργήθηκε από EMDI web API", "city": "Αθήνα", "tax_office": "Αθηνών", "occupation": "Υπηρεσίες", "relation": "Πελάτης", "payment_method": "Μετρητά", "user": "admin", "parent_contact_id": null, "active": 1, "balance": 0, "created": "2026-09-01", "modified": "2026-09-15T12:00:00" } ], "limit": 50, "offset": 0 } } |
With id = 0 you create a new one. With match = code it finds an existing code and updates it. If the city or relation does not exist yet, the system creates it.
| Field | Meaning |
|---|---|
action |
contact.save |
id |
0 = new contact |
match |
code = find/update by code |
data |
contact fields (name, city, …) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
{ "action": "contact.save", "id": 0, "match": "code", "data": { "code": "C001", "name": "Γιάννης", "surname": "Παπαδόπουλος", "company": "Παράδειγμα ΑΕ", "phone": "2100000000", "mobile": "6900000000", "email": "john@example.com", "address": "1 Main St", "area": "Center", "postal_code": "10431", "vat_number": "999999999", "notes": "Δημιουργήθηκε από EMDI web API", "city": "Αθήνα", "tax_office": "Αθηνών", "occupation": "Υπηρεσίες", "relation": "Πελάτης", "payment_method": "Μετρητά", "active": 1 } } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
{ "success": true, "id": 123, "data": { "id": 123, "code": "C001", "name": "Γιάννης", "surname": "Παπαδόπουλος", "company": "Παράδειγμα ΑΕ", "phone": "2100000000", "mobile": "6900000000", "fax": "", "email": "john@example.com", "address": "1 Main St", "area": "Center", "postal_code": "10431", "vat_number": "999999999", "notes": "Δημιουργήθηκε από EMDI web API", "city": "Αθήνα", "tax_office": "Αθηνών", "occupation": "Υπηρεσίες", "relation": "Πελάτης", "payment_method": "Μετρητά", "user": "admin", "parent_contact_id": null, "active": 1, "balance": 0, "created": "2026-09-01", "modified": "2026-09-15T12:00:00" } } |
Deletes the contact. If it has documents or is locked by myDATA, the call fails (nothing is deleted).
| Field | Meaning |
|---|---|
action |
contact.delete |
id |
the contact id |
|
1 2 3 4 |
{ "action": "contact.delete", "id": 123 } |
Example response:
|
1 2 3 4 |
{ "success": true, "id": 123 } |
Reads one product (and barcodes if any). Replace id.
| Field | Meaning |
|---|---|
action |
product.get |
id |
the product id |
|
1 2 3 4 |
{ "action": "product.get", "id": 10 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "success": true, "id": 10, "data": { "id": 10, "code": "P001", "secondary_code": "", "description": "Δείγμα προϊόντος", "retail_price": 10.5, "wholesale_price": 8.0, "unit": "τεμ", "main_category": "Γενικά", "category": "Λοιπά", "vat_rate": 24, "supplier_id": null, "active": 1, "stock_qty": 100, "barcodes": [ "5200000000001" ], "notes": "Δημιουργήθηκε από EMDI web API", "modified": "2026-09-15T12:00:00" } } |
Returns products with filters. You can filter by code.
| Field | Meaning |
|---|---|
action |
product.list |
code |
product code (optional) |
active |
1 = active |
limit / offset |
pagination |
|
1 2 3 4 5 6 7 |
{ "action": "product.list", "code": "P001", "active": 1, "limit": 50, "offset": 0 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
{ "success": true, "data": { "items": [ { "id": 10, "code": "P001", "secondary_code": "", "description": "Δείγμα προϊόντος", "retail_price": 10.5, "wholesale_price": 8.0, "unit": "τεμ", "main_category": "Γενικά", "category": "Λοιπά", "vat_rate": 24, "supplier_id": null, "active": 1, "stock_qty": 100, "barcodes": [ "5200000000001" ], "notes": "Δημιουργήθηκε από EMDI web API", "modified": "2026-09-15T12:00:00" } ], "limit": 50, "offset": 0 } } |
Same logic as contacts: id 0 = new, match:code = update by code. The VAT rate (vat_rate) must already exist in VAT settings.
| Field | Meaning |
|---|---|
action |
product.save |
id |
0 = new |
match |
code |
data |
product fields |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "action": "product.save", "id": 0, "match": "code", "data": { "code": "P001", "description": "Δείγμα προϊόντος", "retail_price": 10.5, "wholesale_price": 8.0, "unit": "τεμ", "main_category": "Γενικά", "category": "Λοιπά", "vat_rate": 24, "active": 1, "notes": "Δημιουργήθηκε από EMDI web API" } } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{ "success": true, "id": 10, "data": { "id": 10, "code": "P001", "secondary_code": "", "description": "Δείγμα προϊόντος", "retail_price": 10.5, "wholesale_price": 8.0, "unit": "τεμ", "main_category": "Γενικά", "category": "Λοιπά", "vat_rate": 24, "supplier_id": null, "active": 1, "stock_qty": 100, "barcodes": [ "5200000000001" ], "notes": "Δημιουργήθηκε από EMDI web API", "modified": "2026-09-15T12:00:00" } } |
Deletes the product. If it is used on documents, the call fails.
| Field | Meaning |
|---|---|
action |
product.delete |
id |
the product id |
|
1 2 3 4 |
{ "action": "product.delete", "id": 10 } |
Example response:
|
1 2 3 4 |
{ "success": true, "id": 10 } |
Reads one calendar event by its id .
| Field | Meaning |
|---|---|
action |
calendar.get |
id |
the calendar event id |
|
1 2 3 4 |
{ "action": "calendar.get", "id": 5 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "success": true, "id": 5, "data": { "id": 5, "contact_id": 123, "event_type": "Ραντεβού", "user": "admin", "assigned_to": "", "start": "2026-09-20T10:00:00", "end": "2026-09-20T11:00:00", "alarm": "2026-09-20T09:45:00", "comment": "Συνάντηση", "repeat": "", "modified": "2026-09-15T12:00:00" } } |
Returns events between two dates. Date format looks like: 2026-09-01 00:00:00.
| Field | Meaning |
|---|---|
action |
calendar.list |
start / end |
from / to |
contact_id |
optional: only for one contact |
limit / offset |
pagination |
|
1 2 3 4 5 6 7 8 |
{ "action": "calendar.list", "start": "2026-09-01 00:00:00", "end": "2026-09-30 23:59:59", "contact_id": 123, "limit": 50, "offset": 0 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
{ "success": true, "data": { "items": [ { "id": 5, "contact_id": 123, "event_type": "Ραντεβού", "user": "admin", "assigned_to": "", "start": "2026-09-20T10:00:00", "end": "2026-09-20T11:00:00", "alarm": "2026-09-20T09:45:00", "comment": "Συνάντηση", "repeat": "", "modified": "2026-09-15T12:00:00" } ], "limit": 50, "offset": 0 } } |
For a new event set id 0 and required start inside data.
| Field | Meaning |
|---|---|
action |
calendar.save |
id |
0 = new |
data.start |
start date/time (required for new) |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "action": "calendar.save", "id": 0, "data": { "start": "2026-09-20 10:00:00", "end": "2026-09-20 11:00:00", "alarm": "2026-09-20 09:45:00", "comment": "Συνάντηση", "contact_id": 123, "event_type": "Ραντεβού" } } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "success": true, "id": 5, "data": { "id": 5, "contact_id": 123, "event_type": "Ραντεβού", "user": "admin", "assigned_to": "", "start": "2026-09-20T10:00:00", "end": "2026-09-20T11:00:00", "alarm": "2026-09-20T09:45:00", "comment": "Συνάντηση", "repeat": "", "modified": "2026-09-15T12:00:00" } } |
Deletes the calendar event.
| Field | Meaning |
|---|---|
action |
calendar.delete |
id |
the calendar event id |
|
1 2 3 4 |
{ "action": "calendar.delete", "id": 5 } |
Example response:
|
1 2 3 4 |
{ "success": true, "id": 5 } |
Reads a document with its lines. If it is locked in myDATA, you can read it but not change it.
| Field | Meaning |
|---|---|
action |
document.get |
id |
the document id |
|
1 2 3 4 |
{ "action": "document.get", "id": 1001 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
{ "success": true, "id": 1001, "data": { "id": 1001, "series": "Α", "number": 42, "date": "2026-09-15T12:00:00", "doc_type_id": 12, "doc_type": "Τιμολόγιο πώλησης", "contact_id": 45, "delivery_contact_id": null, "payment_method": "Μετρητά", "user": "admin", "comment": "Δοκιμή API", "notes": "", "plate": "", "related": "", "net": 21.0, "total": 26.04, "mydata_locked": false, "modified": "2026-09-15T12:00:00", "lines": [ { "id": 501, "product_id": 10, "code": "P001", "description": "Δείγμα προϊόντος", "unit": "τεμ", "qty": 2, "price": 10.5, "discount": 0, "vat_rate": 24, "order": 1 } ] } } |
Returns documents. With include_lines true you also get product lines.
| Field | Meaning |
|---|---|
action |
document.list |
contact_id / doc_type_id |
filters |
date_from / date_to |
dates |
include_lines |
true = include lines |
|
1 2 3 4 5 6 7 8 9 10 |
{ "action": "document.list", "contact_id": 123, "doc_type_id": 12, "date_from": "2026-09-01", "date_to": "2026-09-30", "include_lines": true, "limit": 20, "offset": 0 } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
{ "success": true, "data": { "items": [ { "id": 1001, "series": "Α", "number": 42, "date": "2026-09-15T12:00:00", "doc_type_id": 12, "doc_type": "Τιμολόγιο πώλησης", "contact_id": 45, "delivery_contact_id": null, "payment_method": "Μετρητά", "user": "admin", "comment": "Δοκιμή API", "notes": "", "plate": "", "related": "", "net": 21.0, "total": 26.04, "mydata_locked": false, "modified": "2026-09-15T12:00:00", "lines": [ { "id": 501, "product_id": 10, "code": "P001", "description": "Δείγμα προϊόντος", "unit": "τεμ", "qty": 2, "price": 10.5, "discount": 0, "vat_rate": 24, "order": 1 } ] } ], "limit": 20, "offset": 0 } } |
For a new document you need: document type (doc_type_id), contact (contact_id) and at least one line in lines. Get the correct ids from meta.lookups. Types with tax transmission (tax_docs) cannot be created via the API.
| Field | Meaning |
|---|---|
action |
document.save |
id |
0 = new |
data.doc_type_id |
document type |
data.contact_id |
contact |
data.lines |
product lines (at least one for new) |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "action": "document.save", "id": 0, "data": { "doc_type_id": 12, "contact_id": 45, "date": "2026-09-15 12:00:00", "payment_method": "Μετρητά", "comment": "Δοκιμή API", "lines": [ { "code": "P001", "description": "Δείγμα προϊόντος", "qty": 2, "price": 10.5, "discount": 0, "vat_rate": 24, "unit": "τεμ", "order": 1 } ] } } |
Example response:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
{ "success": true, "id": 1001, "data": { "id": 1001, "series": "Α", "number": 42, "date": "2026-09-15T12:00:00", "doc_type_id": 12, "doc_type": "Τιμολόγιο πώλησης", "contact_id": 45, "delivery_contact_id": null, "payment_method": "Μετρητά", "user": "admin", "comment": "Δοκιμή API", "notes": "", "plate": "", "related": "", "net": 21.0, "total": 26.04, "mydata_locked": false, "modified": "2026-09-15T12:00:00", "lines": [ { "id": 501, "product_id": 10, "code": "P001", "description": "Δείγμα προϊόντος", "unit": "τεμ", "qty": 2, "price": 10.5, "discount": 0, "vat_rate": 24, "order": 1 } ] } } |
Deletes the document and its lines. If it is locked in myDATA, the call fails.
| Field | Meaning |
|---|---|
action |
document.delete |
id |
the document id |
|
1 2 3 4 |
{ "action": "document.delete", "id": 1001 } |
Example response:
|
1 2 3 4 |
{ "success": true, "id": 1001 } |
Notes
- Always start with
meta.lookupsbefore you create documents. - The
idis the internal number andcodeis the code you see on screen. - To update by code use
"match": "code". - Documents locked in myDATA cannot be changed or deleted via the API.
