- Home
- Developers
EVOR REST API v1
The same API the EVOR mobile app uses. Versioned, tenant-scoped and permission-checked on every call.
Authentication
Every call sends Authorization: Bearer <token>. There are two kinds of token, and both belong to exactly one workspace. A tenant id sent in a header or body is ignored — the workspace always comes from the token.
Mobile and app sign-in
Exchange an email and password for a token that lasts 30 days. If the person works in more than one workspace, the first call returns the list and you sign in again with membership_id.
POST /api/v1/auth/login
{ "email": "tech@yourcompany.ae", "password": "••••••••", "device_name": "Pixel 8" }
201 Created
{ "data": { "token": "12|Vb3…", "token_type": "Bearer", "expires_at": "2026-11-02T10:00:00+00:00",
"membership_id": "01j…", "workspace": "Gulf Breeze Technical Services" } }
409 Conflict → { "error": { "code": "choose_workspace" }, "workspaces": [ { "membership_id": "01j…", "tenant": "…" } ] }
POST /api/v1/auth/logout revokes the token. Five wrong passwords lock the account for 15 minutes.
Workspace API keys
For server-to-server work, an owner or admin creates a key in Connect → API keys. Keys start with evk_, are shown once, and act with the permissions of the person who created them. A key with only the read scope can call GET endpoints and nothing else. API access depends on your plan.
Conventions
- Base URL
https://<your-evor-domain>/api/v1. JSON in and out; sendAccept: application/json. - IDs are 26-character ULIDs. Times are ISO 8601 in UTC; dates are
YYYY-MM-DD. Datetime fields you send without an offset are read in the workspace timezone. - Permissions are the same as on screen. A technician's token sees only assigned jobs; a sales token sees only the records its role allows. Records outside your scope return
404, never403, so their existence is not revealed. - Idempotency. Any write can send
Idempotency-Key: <uuid>. Repeating the call with the same key returns the first response withIdempotent-Replayed: trueinstead of running again. The mobile app uses this for its offline queue. - Client time. Field actions accept
client_time(when it happened on the device) as well as the server's own time. - Optimistic concurrency. Records carry a
version. Send the version you read onPATCH; if someone saved in between you get409 version_conflictwith the current record. - Lists are paginated:
?page=2&per_page=50(max 100). The response includesmeta.totalandmeta.last_page. Useupdated_sincefor incremental sync. - Rate limit: 240 requests per minute per token. Each response carries
X-Correlation-Id— quote it when you contact support.
Errors
Errors always have the same shape:
422 Unprocessable Content
{ "error": { "code": "validation_failed", "message": "The legal name field is required.",
"fields": { "legal_name": ["The legal name field is required."] } } }
| Status and code | Meaning |
|---|---|
401 session_invalid | Token missing, expired or revoked, or the member was deactivated. Sign in again. |
402 plan_limit | The workspace reached a plan limit (users, records, storage). |
403 forbidden | The role cannot do this, the module is not enabled, or the status does not allow the change. |
404 not_found | The record does not exist or is outside your scope. |
409 conflict, version_conflict, possible_duplicate | The record changed, the action no longer applies, or a similar record exists (send ignore_duplicates=true to continue). |
422 validation_failed | Field errors are in error.fields. |
429 rate_limited | Slow down; retry after the Retry-After seconds. |
Records
Every record type in EVOR uses the same endpoints. {resource} is the type key — for example customers, contacts, leads, opportunities, quotations, work_orders, service_requests, assets, amc_contracts, invoices, payments, products, purchase_orders, tasks. GET /schema lists the types your token can open, with every field, its type and whether it is required.
| Endpoint | What it does |
|---|---|
GET/{resource} | List. Filters: q (search), view (saved view such as open or overdue), status and other listed filters, updated_since, sort, dir, page, per_page. |
POST/{resource} | Create. Fields you leave out take the workspace defaults. |
GET/{resource}/{id} | Read one record, including the status changes you are allowed to make (transitions). |
PATCH/{resource}/{id} | Update only the fields you send. Send version to avoid overwriting someone else. |
POST/{resource}/{id}/transition | Change status: { "to": "qualified" }. Some changes need { "reason": "…" }. Approvals and lifecycle rules apply. |
DELETE/{resource}/{id} | Delete where your role allows it. Linked records cannot be deleted; mark them inactive. |
GET/{resource}/{id}/timeline | Audit trail, activities and comments, newest first. |
POST/{resource}/{id}/comments | Add an internal comment: { "body": "…" }. |
POST/{resource}/{id}/activities | Log a call, email, WhatsApp, meeting, visit or note; add due_at to schedule a follow-up. |
POST/{resource}/{id}/files | Upload a file (multipart, field "file", up to 20 MB). Files are private to the workspace. |
curl https://app.example.ae/api/v1/customers?q=noor&per_page=10 \
-H "Authorization: Bearer evk_…" -H "Accept: application/json"
{ "data": [ { "id": "01j…", "number": "CUS-2026-0001", "label": "CUS-2026-0001 Al Noor Facilities Management LLC",
"status": "active", "version": 3, "legal_name": "Al Noor Facilities Management LLC", "credit_days": 30,
"owner_membership_id": "01j…", "owner_membership_id_label": "Omar Haddad", … } ],
"meta": { "page": 1, "per_page": 10, "last_page": 1, "total": 1 } }
Field app
These endpoints power the EVOR mobile app for technicians and supervisors. A person must accept a job before working on it.
| Endpoint | What it does |
|---|---|
GET/field/jobs | My jobs for today plus anything still open. ?day=2026-10-05 for a specific day. |
GET/field/jobs/{id} | Job detail: site and directions, contact, team and responses, checklist, materials, completion. |
POST/field/jobs/{id}/respond | { "response": "accepted" } or { "response": "declined", "reason": "…" }. |
POST/field/jobs/{id}/attendance | Check in or out: event, lat, lng, accuracy, client_time. Without location, send permission_state and exception_reason; the supervisor is told. |
POST/field/jobs/{id}/checklist | { "answers": [ { "id": "…", "response": "1" } ] }. |
GET/field/stock | Stock I can use: my van first, then the main store. ?q= to search. |
POST/field/jobs/{id}/materials | Record material used: product_id, quantity, optional warehouse_id. Stock moves out of the warehouse. |
POST/field/jobs/{id}/photos | Upload a site photo (multipart "file", captured_at). |
POST/field/jobs/{id}/complete | result (completed or partial), work_done, findings, recommendations, customer_signed_name, signature (PNG data URL), client_time. Required checklist items must be answered. |
Workspace
| Endpoint | What it does |
|---|---|
GET/me | The signed-in person, workspace, enabled modules, permissions and workspace terms. |
GET/home | The figures on this person's command centre, using the same definitions as the web app. |
GET/search?q= | Search customers, contacts, leads, quotations, jobs, invoices, requests and assets. |
GET/options/{resource} | Picker values, scoped by permission. ?parent=customer_id&value=… for dependent lists such as sites. |
GET/notifications | Notifications, newest first. ?unread=1. POST /notifications/read with ids, or no ids to mark all. |
GET/approvals | Approvals waiting for me. POST /approvals/{id}/decide with decision and comment. |
POST/devices | Register a push token: platform (android, ios), push_token, app_version. |
Webhooks
Add an endpoint in Connect → Webhooks and choose events such as quotation.accepted, work_order.completed, invoice.overdue or payment.received. EVOR sends a POST with this body:
{ "event_id": "01j…", "schema_version": 1, "event": "work_order.completed",
"tenant": "gulf-breeze", "actor": "tech@yourcompany.ae",
"subject": { "type": "work_order", "id": "01j…", "label": "WO-2026-0042 Barrier arm service", "version": 7 },
"occurred_at": "2026-10-03T13:05:11+00:00", "correlation_id": "01j…" }
Fetch the full record with GET /api/v1/{resource}/{id} when you need more than the summary.
Each request carries X-Evor-Event, X-Evor-Delivery (use it to ignore repeats), X-Evor-Timestamp and X-Evor-Signature. Verify the signature before trusting the body:
expected = "sha256=" + hex(hmac_sha256(secret, timestamp + "." + raw_body))
accept only if constant_time_equals(expected, X-Evor-Signature) and timestamp is within 5 minutes
Reply with any 2xx within 10 seconds. Failed deliveries are retried after 1, 5 and 30 minutes, then 2, 6 and 24 hours. An endpoint that fails 20 times in a row is paused and shown as unhealthy in Connect.