Skip to content

API ERPJS

API (Application Programming Interface) is a set of tools for automating work with the ERPJS system. It allows you to create, read, update, and delete records in any register, run reports, work with files, and receive real-time updates via WebSocket.

Address

All paths below are relative to your system's address — the one you open ERPJS at in the browser (of the form https://s.erpjs.biz/your-system). There is no separate prefix such as /v1.

Authentication

Obtaining a Token

A JWT token is required to work with the API. Signing in issues it:

curl -X POST https://s.erpjs.biz/your-system/public/login \
     -H "Content-Type: application/json" \
     -d '{"login": "your_login", "password": "your_password"}'
The response contains the jwt field — this is the token for subsequent requests. The company is not passed in the request: the server determines it and puts it into the token. The token is valid for 8 hours.

Instead of a password you can sign in with the user's MCP token (created by the "Generate MCP token" item in the user card): POST /public/mcp-login with the body {"token": "<MCP token>"} returns the same kind of jwt.

Every sign-in ends the user's previous sessions. An integration and a person under the same login knock each other out — create a separate user for the integration. The "API token" in the erpjs.biz cabinet belongs to a different service and does not work for the system's /records/*.

Passing the Token

The token is passed in an HTTP header: Authorization: Bearer <token>. The server does not read cookies to check the token.

Example

export TOKEN="<your_token>"
export BASE_URL="https://s.erpjs.biz/your-system"

curl -X POST $BASE_URL/records/list \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"datadef": "Contacts", "query": {"limit": 10}}'

Working with Records

All record operations are performed via POST requests to /records/* endpoints.

Record List

POST /records/list — retrieve records from a register with filtering, sorting, and pagination.

{
  "datadef": "Invoices",
  "query": {
    "where": { "fOKFlag": 1 },
    "order": [["fInvDate", "DESC"]],
    "limit": 50,
    "offset": 0,
    "search": "search text"
  }
}

The response is an array of records (without a total count). Only the keys where, order, limit, offset, search are allowed in query. The server returns no more than 10,000 records per request — beyond that the answer is 400; page through with limit and offset.

POST /records/sqlList — extended list with specific field selection and grouping (query.groupBy). The fields list is required.

{
  "datadef": "Invoices",
  "fields": ["fSerNr", "fCustCode", "fSum4", "fInvDate"],
  "query": {
    "where": { "fOKFlag": 1, "fInvDate": { "$gte": "2026-01-01" } },
    "order": [["fInvDate", "DESC"]],
    "limit": 100,
    "offset": 0,
    "search": { "search": "Customer", "column": "fCustCode" }
  }
}

Response: {"res": [...], "qty": "1234"} — an array of records and the total count (qty comes as a string).

Give the where condition as an object: values are compared as they are, operators carry $ ($in, $gte, $like and so on); bare in or gt is a 400 error. The server still accepts an SQL string too, but that is a deprecated form: it may be switched off, and it must start with the word WHERE.

Viewing a Record

POST /records/show — retrieve full record data, including matrices, files, and links.

{
  "datadef": "Invoices",
  "id": 123
}

The response contains: - All record fields - each matrix as a separate key under its own name, e.g. "InvoicesMatrix": {"headers": [...], "payload": [...]} (item lines in an invoice) - __files — number of attached files - __links — number of linked documents - __lock — present only when the record is locked: true — by your lock, a login — by someone else's - __lastUpdated — time of last update - as well as __permissions (rights on the record and its fields) and __revision

Default Values

POST /records/defaults — retrieve default values for a new record.

{
  "datadef": "Invoices"
}

Creating a Record

POST /records/create — create a new record. Matrices are passed in the same request.

{
  "datadef": "Contacts",
  "data": {
    "fCode": "C-001",
    "fName": "New Company",
    "fCUType": 1,
    "fPhone": "+380501234567"
  }
}

Example of creating a record with a matrix (matrix line fields also start with f):

{
  "datadef": "Invoices",
  "data": {
    "fCustCode": "C-001",
    "fInvDate": "2026-03-28",
    "fCurncyCode": "UAH",
    "InvoicesMatrix": {
      "payload": [
        {
          "fArtCode": "ITEM-001",
          "fQuant": 5,
          "fPrice": 100
        }
      ]
    }
  }
}

Updating a Record

POST /records/update — update an existing record.

{
  "datadef": "Contacts",
  "id": 123,
  "data": {
    "fPhone": "+380509876543"
  }
}

Lock the record first. update accepts only a record you have opened for editing with setLock (see "Record Locking"); without a lock the answer is 423 "record is not open for editing", and under someone else's lock it is 423 too.

Send only the fields you are changing. A field that is absent from data is treated as untouched and keeps the value stored in the document; a matrix absent from data is not touched either. A field you send is written as it is and can overwrite a change someone made between your read and your write. A matrix you send replaces the previous one entirely.

Dates

A date-and-time value comes in the records/show response with the Z label (for example, 2026-09-15T07:05:00Z). If you send it back unchanged, the system recognises the unchanged value and keeps it as it was (the kernel works this way since 26.09.2026). A shift can happen only if someone else changed the value between your read and your write — so the main rule stays: do not send a date you did not change.

The ERPJS MCP connector (the update_record tool) also recognises a date you have read and are sending back unchanged; with applyWactions: false the body goes to the system verbatim.

A new value:

  • in the response form (2026-09-15T07:05:00Z) is read as a real instant — 10:05 in Kyiv;
  • with an explicit offset: 2026-09-16T08:45:00+03:00 is stored as 08:45 Kyiv time;
  • as DD/MM/YYYY ("17/09/2026") or YYYY-MM-DD — as a calendar day with no shift; in a date-and-time field the time becomes 00:00. To set the time, use DD/MM/YYYY HH:mm or YYYY-MM-DD HH:mm:ss — this is the server's local time.

Date-only fields (DD/MM/YYYY in the window, and DD/MM/YYYY in the show response too) are not affected by any shift.

Deleting a Record

POST /records/delete — delete a record.

{
  "datadef": "Contacts",
  "id": 123
}

Note: a record with attached files cannot be deleted. Remove the files first (softDeleteAttachment).


Metadata and Data Model

POST /system/model — retrieve the structure (schema) of a register: field list, types, validations.

{
  "datadef": "Invoices"
}

POST /system/meta — retrieve record metadata: __lock, __files, __links, __actions, __lastUpdated, __revision, __permissions.

{
  "datadef": "Invoices",
  "id": 123
}

Reports

POST /reports/<Report>/<view> — run a report with parameters. For most reports the view is simple (for example, /reports/SalesReport/simple).

{
  "fFrom": "2026-01-01",
  "fTo": "2026-03-31",
  "fCustCode": "C-001",
  "_meta": {
    "htmlFast": true
  }
}

The _meta parameter sets how the report answers: - htmlFast: true — the answer comes at once as an HTML string; together with getContext: true — the report data as JSON; - without htmlFast the answer is only a {key}: the result arrives as WebSocket events, and the files are fetched via GET /system/download-report; - disableHtml: true, disableXlsx: true — skip building the HTML or the Excel file in such a full report.

The server checks report parameters the same way the report window does: a period whose start is after its end, a code missing from the reference list, a document number that is not a number. In that case the report is not built, and the response is a validation error with code 400; the field name is in data.field.


Files

Uploading a File

POST /system/upload — upload a file and attach it to a record. Use multipart/form-data.

Form fields: - file — the file - data — JSON with parameters: Register, RecordId, FileRealName, FileSize, CHECKSUM (sha256 in hex)

You need rights on the record's register; the file size limit is 10 MB.

Downloading a File

GET /system/download/<uuid> — download a file by its UUID.

Deleting a File

POST /system/softDeleteAttachment — mark a file as deleted. Body: {"datadef": "Attachments", "id": <file id>}; you need the right to edit the record the file is attached to.


Record Locking

The locking system prevents simultaneous editing of a record by multiple users. A lock is required for records/update.

  • POST /system/setLock — lock a record for editing
  • POST /system/checkLock — check whether a record is locked and by whom
  • POST /system/removeLock — remove the lock
  • GET /system/lockList — list of all active locks

A lock lives for 5 minutes. Calling setLock again as the same user extends it by another 5 minutes — the editing window does this every 10 seconds. If your work with the record takes longer, repeat setLock; a lock that was not removed in time (the integration crashed, the tab was closed) expires by itself.


Access Permissions

The body carries a list of names — without it the answer is 400.

  • POST /system/getPermissionsRegisters {"registers": [...]} — register permissions (list, read, create, edit, remove and more)
  • POST /system/getPermissionsModules {"modules": [...]} — module permissions
  • POST /system/getPermissionsReports {"reports": [...]} — report permissions
  • POST /system/getPermissionsMaintenances {"maintenances": [...]} — maintenance procedure permissions

WebSocket (Real-Time)

ERPJS supports WebSocket connections via Socket.IO for receiving real-time updates.

Connection: Socket.IO at the path <system address>/socket.io, with the token in the handshake: auth: {token: "<token>"}.

Server events: - system_version — server version (on connection) - push:<login> — the user's unread notifications (the answer to :live:) - update-tasks — task list update

Client events: - :live: — activity confirmation and a request for unread notifications - push:success — confirmation that notifications were received (an array of their ids) - get:activeUsers — request the list of active users (the answer comes through a callback)


Procedures and Maintenance

POST /maintenances/<name>/<task> — run a maintenance procedure (import, export, bulk operations); for most services the task is first. The server checks the role's rights on the service.

POST /custom/<name> — run a server procedure; only procedures declared in the system become routes.


Public Endpoints (No Token Required)

Some endpoints are available without authentication:

  • POST /public/login — log in to the system
  • POST /public/mcp-login — log in with an MCP token
  • GET /public/qrDownloadFiles — download files via QR code
  • /openserver/<route> (GET or POST) — open API endpoints

Everything under /public and /openserver, sign-in included, is rate-limited: 10 requests per 30 seconds from one IP to one path, after that the answer is 429.


Response Codes

Code Description
200 Successful request
400 Validation error (details in the response body: message, data)
401 Invalid or missing token, or the session was replaced by a newer sign-in
403 Insufficient access permissions
404 Register or record not found
423 The record is not locked by you (no setLock) or is held by someone else
429 Rate limit exceeded
500 Internal server error

Integration Tips

  1. Pagination: use limit and offset for large data sets. The sqlList endpoint returns qty — the total number of records; list returns at most 10,000 records per request.

  2. Matrices: when creating or updating a document with items (invoice, order), pass the matrix in the field with the corresponding name (e.g., InvoicesMatrix) as {"payload": [...]}.

  3. Validation: the server performs business logic validation (recordCheck). Validation errors are returned with code 400; the name of the field with the error is in data.field.

  4. Locking: before updating a record, set a lock (setLock) — without it update answers 423 — and remove it afterwards (removeLock).

  5. Transactions: record creation and updates are executed within transactions. On error, all changes in the database are rolled back automatically.

  6. A separate user: the integration's sign-in logs out a person with the same login, and vice versa.