xMatix
Sign in Request demo
xMatix
PRODUCTS
SalesField Sales & SFACRMRewardsClaimsInventoryProcurementWarehouse ManagementField ServiceServiceSupportTelephony & MessagingFinance & AccountingPayrollExpense ManagementCommercePortalsAnalytics & ReportingData StudioMobile AppSee all products →
PLATFORM
Platform overviewApp BuilderAutomationIntegrationsSecurity & GovernanceChange ManagementDevelopers
SENSE AI
Sense AI overviewSense AssistSense ControlSense VisionAI StudioTrust & governanceIn Claude & ChatGPTUse cases
SOLUTIONS
Auto DMSConsumer Goods DMSSales Force Automation (SFA) FMCG & DistributionManufacturing & Dealer NetworksAutomotive & DealershipsPharma & HealthcareConsumer DurablesAgri-InputsBuilding MaterialsService NetworksWarehousing & 3PLFinancial AccountingERP SoftwareIndia GST ComplianceUAE VAT & e-InvoicingSaudi ZATCA & VATAll solutions →
RESOURCES
Knowledge CenterDeveloper & CLIBlogGuidesWhat is xMatix?Company facts
COMPANY
AboutCareersPartnersEventsContactAuthorsLegal
Sign in Request demo
Home/Docs/Developer/The data API
REFERENCE · Last reviewed

The data API

The data API is the REST interface the xMatix web and mobile clients run on, and you can use it to read and write any record in your organization from your own code. There is no separate integration API with different rules. Every entity, standard or custom, is reached by its logical name through one set of endpoints under your organization's API address. Requests and responses are JSON. Every request runs as the signed-in user, with the full security model applied on the server. Every write goes through the complete save pipeline: business rules, validations, scripts, workflows and approvals run exactly as they do in the product.

Overview

AreaWhat it gives you
ReadingPages of records with filter, search, sort and paging; single records with related records; related lists
WritingCreate, partial update and delete, plus preview endpoints that show the outcome without saving
ActionsThe same named actions that buttons run in the product, on one record or many, with job tracking for actions that run in the background
Bulk and upsertCreate or update many records in one call; upsert matches existing records by id or external id
Delta syncRecords changed or deleted since a cursor, for systems that keep a copy of xMatix data
MetadataThe data model itself: entities, fields, views and relations

When to use it

  • Integrations. Use it to push orders, customers or transactions in from another system, or to pull records out on a schedule.
  • Mirrors and reporting stores. Use delta sync to keep an external copy current without re-reading whole tables.
  • Your own front ends. Use it to build a portal, a kiosk or a tool on top of xMatix data with the same rules as the product.

For a one-off spreadsheet load, use Import data instead. For moving configuration (entities, scripts, workflows) between environments, use the CLI. For an AI assistant working on your data, use the MCP endpoint.

How xMatix handles it

  • Address and version. Every route has the form https://<your-api-host>/api/1.0/.... The version segment must match the API version the server runs, which is 1.0. A leading v is tolerated (v1.0), but a bare v1 or 1 is rejected with a 400 that names the version parameter.
  • Which organization. The API host decides the organization, the same way it does for the CLI. The token must belong to a user of that organization. Otherwise every call is refused.
  • JSON shape. Property names are camelCase (name, accountId). Enumerations are written as their names, not numbers. Ids are GUIDs.
  • Same pipeline as the product. A create or update runs defaults, validations, business rules, bound scripts, workflow triggers and approval gates. Calculated and roll-up fields are always computed by the server. If your payload includes a value for one of them, that value is dropped.
  • Unknown properties. A property that names no field is ignored without an error. A property that names a field which is switched off (inactive) is rejected, so a value is never silently lost.
  • Security on every row. Lists, related lists and delta sync return only records the user may read. Fields the user may not read come back masked. Writes check the entity grant, record security and field permissions.

Prerequisites

  • A user account for the caller. For an unattended integration, an administrator creates a dedicated user and gives it a security profile with exactly the entities, fields and actions the integration needs.
  • A bearer token. Calls carry an OAuth 2.0 bearer token issued by your organization's sign-in authority, the same identity service that signs users into the product.
  • Logical names. Entity and field names in routes and payloads are logical names (Account, not "Accounts"). Find them in Setup → Design Studio → Entities, with the CLI's metadata pull, or with the MCP endpoint's describe tool.

Procedure

Step 1 — Read a page of records

curl -H "Authorization: Bearer $TOKEN" \
  "https://<your-api-host>/api/1.0/data/Account?pageSize=50&pageNo=1&orderBy=name"

The response wraps the page:

{ "count": 1342, "pageSize": 50, "currentPageNo": 1, "data": [ ... ] }

count is the total across all pages. To walk the whole set, increase pageNo until you have read count records. Use a stable orderBy so the pages do not shift between calls.

GET /api/1.0/data/Account/{id} returns the record, or 404 if it does not exist or you cannot see it. Add ?relations= with relation names to include related records inline. To page through one related list, call GET /api/1.0/data/Account/{id}/{relation}. It takes the same filter, orderBy, ids, pageSize and pageNo parameters as a list.

Step 3 — Create a record

curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{ "name": "Harbor Point Traders", "phone": "+91 98000 00000" }' \
  "https://<your-api-host>/api/1.0/data/Account"

A successful create returns 201 Created with the saved record, including its new id, its generated document number if the entity has one, and every default and calculated value. The Location header points at the new record. To see the result without saving, send the same body to POST /api/1.0/data/{entity}/preview.

Step 4 — Change a record

PATCH /api/1.0/data/{entity}/{id} with only the fields you are changing. Fields you leave out keep their values. The response is the saved record. To see the result first, use PATCH /api/1.0/data/{entity}/{id}/preview.

To make sure you do not overwrite someone else's newer change, send the record's _ts value from your last read in an If-Match header. If the record has changed since then, the update is refused with 410 and code OBJECT_MODIFIED. Read the record again, reapply your change and retry.

Step 5 — Run an action

POST /api/1.0/data/{entity}/{id}/{action}
{ "entity": { "remarks": "Approved by phone" }, "args": { } }

Both parts of the body are optional. entity is a partial update applied before the action runs. args carries any input the action asks for. The action name is the action's name in the entity's metadata. These are the same actions the buttons on a record run.

  • An action that runs immediately returns the updated record.
  • An action set to run in the background returns a job handle: jobId, jobStatus (Pending), entityName, recordId and actionName. Follow it with GET /api/1.0/data/{entity}/{id}/job, and read the record again once the job has finished. If the latest job failed, POST /api/1.0/data/{entity}/{id}/job/retry runs it again.
  • If you press the same action again while its job is still running, you get the running job's handle back with duplicate: true. Nothing new is queued.

To see what an action would do without saving anything, call POST /api/1.0/data/{entity}/{id}/{action}/dry-run with the same body. Where action previews are available to your organization, the dry run runs the action as it really would and then rolls everything back. It reports whether the action would succeed, which fields would change and which messages would show. Nothing is saved or sent.

Step 6 — Delete a record

DELETE /api/1.0/data/{entity}/{id} returns 204 when the record is deleted and 404 when it does not exist. Deleting is normally a soft delete. The record leaves lists and lookups and can be restored from the Recycle Bin until its purge deadline (delete, recycle bin and restore). A record that other records still point to is refused with 409 and code REFERENCE_CONSTRAINT_VIOLATION.

Step 7 — Keep a copy in step with delta sync

  1. Call GET /api/1.0/sync/{entity} without since to start from the beginning.
  2. Store the nextCursor from the response, and keep calling with since=<nextCursor> while hasMore is true.
  3. Apply each record to your copy. A record with a non-empty deletedUtc has been deleted, so remove it from your copy.
  4. On later runs, call again with the last stored cursor. You get only what changed since then.

Endpoint reference

Reading

EndpointWhat it returns
GET /api/1.0/data/{entity}A page of records with count, pageSize, currentPageNo and data
GET /api/1.0/data/{entity}/{id}One record. ?relations= includes related records inline
GET /api/1.0/data/{entity}/{id}/{relation}A page of the records related through a named relation
GET /api/1.0/data/view/{entity}/{viewId}One page of a saved view's rows with hasMore instead of a total. The view's columns and conditions apply, and your filter and orderBy add to them
GET /api/1.0/data/view/{entity}/{viewId}/countThe view's row count, capped (10,000 by default). isCapped: true means "at least this many"

List parameters:

ParameterMeaning
filterA filter expression over the entity's fields, evaluated in the database. This is the query-filter side of Expressions
searchA free-text term. On the list endpoint it matches records whose Name contains the term, and it is combined with filter
orderByA sort expression, for example name or createdDateTime desc
relationsRelated records to include inline
idsComma-separated record ids, to fetch a known set in one call
pageSize, pageNoPaging. pageSize defaults to 25 and is capped by the server (250 in the standard configuration). A larger value is reduced to the cap without an error, so read the echoed pageSize. pageNo starts at 1

Writing

EndpointWhat it does
POST /api/1.0/data/{entity}Creates a record and returns 201 with the record and its location
PATCH /api/1.0/data/{entity}/{id}Partial update. Send only the fields you change
DELETE /api/1.0/data/{entity}/{id}Deletes the record. Returns 204, or 404 if it is not found
POST /api/1.0/data/{entity}/previewReturns the record a create would produce, with defaults, rules and calculations applied, without saving
PATCH /api/1.0/data/{entity}/{id}/previewThe same, for an update

Actions and jobs

EndpointWhat it does
POST /api/1.0/data/{entity}/{id}/{action}Runs a named action on one record. The body is { "entity": {...}, "args": ... }, both optional
POST /api/1.0/data/{entity}/{id}/{action}/dry-runShows what the action would do, then rolls everything back
GET /api/1.0/data/{entity}/{id}/{action}/prefillThe values the action would preset on the record
POST /api/1.0/data/bulk/{entity}/{action}Runs an action on many records. The body is { "items": [ { "id": "...", "entity": {...}, "args": ... } ] }
GET /api/1.0/data/{entity}/{id}/jobThe record's latest background job. Returns 204 when there is none
POST /api/1.0/data/{entity}/{id}/job/retryRuns the record's latest failed job again

Bulk and upsert

EndpointWhat it does
POST /api/1.0/data/bulk/{entity}Creates every record in a JSON array, all or nothing. One refused record rolls the whole batch back
PATCH /api/1.0/data/bulk/{entity}Partially updates every record in a JSON array. Each item must carry its id. Ids that are not found are skipped
DELETE /api/1.0/data/bulk/{entity}/{ids}Deletes the listed records. Returns 204 if all were removed and 404 if any were not. Returns 409 if another record still refers to one of them
POST /api/1.0/data/upsert/{entity}Saves each row of a JSON array on its own. A row with an id updates that record. A row with no id but an externalId updates the one record with that external id, if exactly one exists. Any other row is created. The response lists rowIndex, status (Created, Updated or Error), id and errorMessage for every row

The bulk action endpoint answers { "results": [...] } with one entry per item: status is ok, missing (the record was not found) or error with the message. One failed item does not stop the others.

Delta sync

EndpointWhat it does
GET /api/1.0/sync/{entity}?since=<cursor>Records created, changed or deleted after the cursor, oldest first. pageSize defaults to 100. includeTotal=true adds totalCount
POST /api/1.0/sync/manifestFor a list of { "entity": "...", "since": "..." } pairs, answers in one call whether each entity has anything new (changed) and whether you can read it (accessible)

A delta page carries data, nextCursor, hasMore, retentionHorizonUtc and epoch:

  • retentionHorizonUtc is the oldest point for which deletions are still guaranteed to be reported. If your last full refresh is older than this, re-read the entity in full instead of trusting deltas.
  • epoch changes when the entity's storage is restructured. When it differs from the value you stored, re-read the entity in full.

Metadata

The model itself (entities, fields, views, relations) is available under /api/1.0/metadata/.... This is the surface the CLI's metadata commands use. To discover what fields an entity has, use these routes or the MCP describe tool rather than guessing from record payloads. The same routes also accept changes. Make model changes through the CLI or the setup screens, which validate and track them, rather than by calling these routes yourself.

Business rules

  • Validation. A record that breaks a validation or a required field is refused with 422. The details list names each problem with message, field, fieldLabel and code. A business rule that blocks the save is also refused with 422, with code BUSINESS_RULE_VIOLATION and the block reasons.
  • Duplicates. A value that must be unique and already exists is refused with 409 and code DUPLICATE_VALUE.
  • Edit lock. While another user has the record open for editing in the product, updates and deletes are refused with 409 and code record_locked. The message names the person editing. Retry once they have finished.
  • Approval lock. A record waiting on an approval request cannot be updated or deleted. The API answers 403 with code RECORD_APPROVAL_LOCKED.
  • Background jobs. While a background job is processing a document, other actions and new lines on it are refused with 409 and code record_processing.
  • Retries without duplicates. On a single create or a single action, send a unique X-Mutation-Id header (up to 64 characters) and reuse it on every retry of that same call. If the first attempt already succeeded, the retry returns the existing record (for an action, the record with duplicate: true) instead of doing the work twice. If the first attempt is still running, the retry gets 409 with code mutation_in_flight and a Retry-After header.
  • Read-only organization. When an organization's subscription is read-only, every write is refused with 403 and error tenant_readonly. Reads keep working.

Example

A field-service team's billing system creates customer accounts in xMatix every night and reads back the account numbers.

  1. The integration lists accounts changed since its last run with GET /api/1.0/sync/Account?since=<stored cursor>. It updates its own records, removes any that carry a deletedUtc, and stores the new nextCursor.
  2. For each new customer, it sends POST /api/1.0/data/upsert/Account with rows such as { "externalId": "BILL-20417", "name": "Lakeview Dental Clinic", "phone": "+91 98000 11111" }. The first night, the row is Created. On later nights, the same external id finds the existing account and the row is Updated, so re-sending a file never creates duplicates.
  3. One row comes back with status Error and the validation message in errorMessage. The integration logs that row and carries on with the rest. Every other row in the same call is saved.
  4. When the billing system needs a record action, it calls POST /api/1.0/data/Account/{id}/{action} with the action's name as it appears in the entity's actions. The integration user's security profile grants that action and nothing else.

Training

Practice exercise

In a sandbox organization, using a test user with read and create rights on Account:

  1. List the first 10 accounts sorted by name. Note the count.
  2. Create an account called "Training Account 01" through the preview endpoint, and confirm nothing new appears in the product's account list.
  3. Create the same account for real. Expected result: 201, a new id, and the account visible in the product.
  4. Update its phone number with PATCH, sending the _ts from step 3 in If-Match. Then repeat the same request with the same old _ts. Expected result: the first succeeds, and the second gets 410 OBJECT_MODIFIED.
  5. Delete it, and find it in the Recycle Bin.

Quick reference

  • Routes start with /api/1.0/, not /api/v1/.
  • Logical names only: Account, accountId, never on-screen labels.
  • pageSize defaults to 25 and is capped by the server. Read the echoed value.
  • search on a list matches Name only. Use filter for anything else.
  • Send If-Match with _ts to avoid overwriting newer changes.
  • Send X-Mutation-Id on creates and actions you may retry.
  • Bulk create is all or nothing. Upsert and bulk actions report per row.
  • Background actions return a job. Follow it with .../{id}/job.
  • For mirrors, use delta sync and keep nextCursor.

Permissions

The API applies the calling user's access with no exceptions:

To do thisThe caller needs
List, read, related lists, delta syncRead on the entity. Rows are limited by record security, and unreadable fields are masked
Create, bulk create, upsert of new rowsCreate on the entity, and create permission on every field sent
Update, bulk update, upsert of existing rowsUpdate on the entity and the record, and update permission on every field sent
DeleteDelete on the entity and the record
Run an actionUpdate on the entity and the record, and the grant for that action

A caller without the access gets 403 with a code such as ENTITY_ACCESS_DENIED, FIELD_ACCESS_DENIED (with the refused fields in deniedFields) or SERVER_ACTION_ACCESS_DENIED. An entity whose product is not enabled for the organization answers PRODUCT_NOT_LICENSED to everyone. Records the caller cannot see simply do not appear, and a single record you cannot see answers 404.

Configuration

Administrators control what an integration can do entirely through the product's own settings. There is no separate API switch.

  • Integration user and security profile. Create a dedicated user and assign a security profile under Security profiles. Grant only the entities, operations, fields and actions the integration needs.
  • Entity operations. An entity that has an operation switched off (for example, no delete) refuses it through the API with ENTITY_OPERATION_DISABLED, for every caller.
  • Actions. Which actions exist, what they are called, and whether they run immediately or in the background are set on the entity's actions in Setup → Design Studio → Entities. A background action returns a job handle instead of the record.
  • Validations, business rules and scripts. These apply to API writes exactly as to the product's forms. A rule added for users also constrains every integration. See what runs when you save a record.
  • Inactive fields. Switching a field off makes writes that set it fail. Reactivate the field or remove it from the integration's payload.
  • Deleted records. Soft-deleted records stay restorable in the Recycle Bin until their Purge After date. See delete, recycle bin and restore.

Common problems

Every call fails with a 400 about the version

The route uses v1 or 1. Use 1.0, as in /api/1.0/data/Account.

Every call fails with 401 or 403 before any data is touched

A 401 not_authenticated means the token is missing or expired, or was issued for a different API address. A 403 tenant_forbidden means the token's user does not belong to the organization of the API host you are calling. Get a token for the right address and the right user. See the developer troubleshooting page.

A 415 naming the entity

The entity name in the route is not a known logical name. Check the spelling and case in Setup → Design Studio → Entities.

A 400 about a filter or orderBy

The expression names a field that does not exist, or a field that is computed and has no stored value to query on. The message names the field. Filter or sort on a stored field instead. Computed fields are still returned in results.

My search returns records I did not expect, or misses some

On the list endpoint, search only looks at Name. To match other fields, write a filter.

I asked for 1,000 records but got fewer

pageSize is capped by the server. The response's pageSize shows the size actually used. Page with pageNo until you have read count records.

A value I sent was not saved

Check whether the property name matches a field's logical name. Unknown names are ignored without an error. Calculated and roll-up fields are always recomputed by the server, so values sent for them are dropped.

A 422 when creating or updating

A validation, a required field or a business rule refused the record. Read details: each entry names the field and the reason. If the code is field_inactive, the field is switched off. Remove it from the payload or ask an administrator to reactivate it.

A 409 when updating or deleting

Read the code. record_locked means another user is editing the record, so retry later. record_processing means a background job is working on the document. DUPLICATE_VALUE means a unique value already exists. REFERENCE_CONSTRAINT_VIOLATION means other records still refer to the one you are deleting.

A 410 OBJECT_MODIFIED

Your If-Match value is older than the record. Read it again, reapply your change and retry.

An action returned a job instead of the record

The action is set to run in the background. Poll GET /api/1.0/data/{entity}/{id}/job until the job finishes, then read the record.

A 429 or 408

The request exceeded the server's per-request budget for time or volume. Ask for smaller pages, split bulk calls into smaller batches, and retry.

Common questions

Do API writes trigger the same automation as the product?

Yes, identically. A record created through POST /api/1.0/data/{entity} goes through the same lifecycle as a form save: defaults, validations, business rules, bound scripts, workflow triggers and approval gates. This cuts both ways. Your integration gets the organization's business logic for free, and a validation that blocks users blocks the integration too. If an integration suddenly starts getting 422 responses, check whether someone added a rule. What runs when you save a record explains the order.

How should an integration page through large tables?

Use pageSize and pageNo with a stable orderBy for one-off reads, and delta sync for anything that keeps a copy continuously. A nightly job that fetches everything re-reads mostly unchanged rows. The same job on sync/{entity} reads only what changed, and the manifest endpoint tells it in one call which entities are worth visiting. Remember that the server caps pageSize, so always read the echoed value.

How do I avoid creating duplicates when a call times out?

Send a unique X-Mutation-Id header on each create or action, and reuse the same value when you retry that call. If the first attempt actually succeeded, the retry returns the record it created instead of creating another. For batch loads, use the upsert endpoint with externalId: re-sending the same rows updates the existing records rather than adding new ones.

Why does a filtered query return records my filter should exclude?

First check the field name. Filters use logical field names, and a misspelt name is refused with a 400 that names it. Then check the value. Picklists compare stored values rather than display labels, and lookups compare ids. If a filter still misbehaves, reproduce it with a small query and compare it against a saved view with the same condition in the product. The query-filter reference covers the rules both use.

Can an integration see more than its user can?

No. The API applies the same security profiles, record security and field permissions as the product. Records the user cannot read never appear, and fields they cannot read come back masked. To widen or narrow what an integration sees, change its user's security profile.