ZuloOne
English
Sign in
Menu

Call ZuloOne from another system

Updated

You are writing a shop, a WMS, or an iPaaS. You create and read records on a ZuloOne tenant over HTTP. You do not edit C# in the workspace. The tenant does not issue your token.

Note

Take the token from login.getzulo.com, then call the tenant. The office SPA still uses POST /api/auth/login (cookies). That is not the integrator door. Cabinet and control-panel JWTs omit resource, so aud stays client_id and the tenant rejects them.

How to#

1. Get a token. aud equals resource. The URL must be on the client's allow-list.

POST https://login.getzulo.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=integrator
&client_secret=$CLIENT_SECRET
&resource=https://acme.getzulo.com
curl -sS -X POST 'https://login.getzulo.com/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=client_credentials' \
  --data-urlencode 'client_id=integrator' \
  --data-urlencode "client_secret=$CLIENT_SECRET" \
  --data-urlencode 'resource=https://acme.getzulo.com'

Send Authorization: Bearer … on every tenant call. Bearer wins and skips the office CSRF gate (X-ZuloOne-Client). The email in the token must be an active MetaUser. An integration account is not Developer.

2. Call a published collection. The public contract is OData: /odata/{PublicCollectionName}. Empty PublicCollectionName means the object Name. GET /odata/$metadata lists the collections this tenant published. If nothing answers, someone on the tenant must create a data entity first.

The examples below use collection Items and key MetaId — that is one published projection of the item dictionary, not a platform default. Source required fields still apply on POST (Name, ItemGroup, UnitOfMeasure). ItemGroup and UnitOfMeasure are the record GUIDs, not the captions. ID is issued by the number sequence; do not send it unless the entity maps it as writable.

GET https://acme.getzulo.com/odata/Items?$filter=Name eq 'Sugar'&$select=ID,Name&$top=20
Authorization: Bearer $ACCESS_TOKEN
curl -sS -G 'https://acme.getzulo.com/odata/Items' \
  --data-urlencode "\$filter=Name eq 'Sugar'" \
  --data-urlencode '$select=ID,Name' \
  --data-urlencode '$top=20' \
  -H "Authorization: Bearer $ACCESS_TOKEN"

$filter, $select, $top, $skip, $count=true work on stored fields. $count=true puts @odata.count next to value. A computed field in $filter is 400.

Create a row with POST to the collection. Update by key with PATCH.

POST https://acme.getzulo.com/odata/Items
Authorization: Bearer $ACCESS_TOKEN
Content-Type: application/json

{
  "Name": "Sugar",
  "ItemGroup": "11111111-1111-1111-1111-111111111111",
  "UnitOfMeasure": "22222222-2222-2222-2222-222222222222"
}
PATCH https://acme.getzulo.com/odata/Items(aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
Authorization: Bearer $ACCESS_TOKEN
Content-Type: application/json

{"Name": "Sugar, white, 1 kg"}
curl -sS -X POST 'https://acme.getzulo.com/odata/Items' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"Name":"Sugar","ItemGroup":"11111111-1111-1111-1111-111111111111","UnitOfMeasure":"22222222-2222-2222-2222-222222222222"}'

curl -sS -X PATCH \
  'https://acme.getzulo.com/odata/Items(aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"Name":"Sugar, white, 1 kg"}'

GET/PATCH/DELETE of one row is /odata/{collection}({key}).

3. Read the status.

Code Meaning
401 Signature or aud is not this instance (ZuloOne:PublicUrl)
403 Email not in MetaUsers, account blocked, or no Read/Create/Update/Delete
404 Unknown collection or IsActive=false
405 IsReadOnly and you called POST/PATCH/DELETE
400 $filter on a computed field

RequireExplicitRoles with an empty matrix is otherwise public over HTTP. Turn the flag on when that must not happen. Source-table RLS still applies. A script on the tenant using IDataEntityManager does not need the HTTP grant; you, over HTTP, do.

4. An operation, not a row. Same Bearer, different URL: POST /api/rest/{Name}. That is a web service (isEnabled, isDevOnly, requireExplicitRoles), not a collection. The same MetaUser is used on /odata, /api/rest/{Name}, and /api/data/{table}. Prefer OData for records. /api/data/{table} is the internal table shape — not the public contract.

What's not there yet#

There is no inbound webhook you can register so ZuloOne calls you back. CompletionHandler on an outbound channel is reserved and unread — see Send an outbound call. Poll OData, or the tenant enqueues outbound to a named channel.

No GraphQL. Do not treat /api/data/{table} as the public contract.

Where next#

Was this page helpful?