Build with Asetavo
A per-workspace REST API over the same register, permissions and audit trail as the app. Sign in, get a token, and read or write assets, movements, stock-takes, maintenance and depreciation — plus signed webhooks and signed reader ingest.
BASE="https://app.asetavo.com/api/w/<your-workspace>"
curl -s -X POST "$BASE/login" \
-H "Content-Type: application/json" \
-d '{"email":"integration@yourco.com","password":"••••••••","label":"ERP sync"}'https://app.asetavo.com/api/w/<your-workspace>Each workspace has its own API host. We give you the exact base URL when we enable API access for your workspace (Business plan and above). Everything below is relative to it.
From zero to your first call
Every response uses the same envelope, so one small wrapper handles the whole API.
Ask your workspace owner (or us) for a dedicated user with only the capabilities your integration needs.
Store the access and refresh tokens and the device_uuid. The access token is valid for up to 12 hours.
Send Authorization: Bearer <access>. Read success, message, data and, for lists, meta.
{
"success": true,
"message": "Login successful",
"data": {
"access": "eyJhbGciOiJIUzI1NiIs...",
"refresh": "8f3c1e…",
"token_type": "Bearer",
"expires_in": 43200,
"device_uuid": "2b7f…",
"user": { "id": 12, "name": "ERP Sync", "email": "integration@yourco.com", "admin": false }
}
}{
"success": true,
"message": "OK",
"data": [ { "id": 118, "tag_no": "AST-000118", "name": "Dell Latitude 5440", ... } ],
"meta": { "page": 1, "per_page": 50, "total": 214, "total_pages": 5 }
}User tokens, revocable sessions
The API authenticates users, not apps. POST /login returns a short-lived JWT access token (up to 12 hours) and an opaque refresh token (up to 30 days) bound to a device session. POST /auth/refresh rotates both — the previous refresh token stops working.
Every request re-checks that the user is still active and the device session hasn’t been revoked, so revoking a session in the app (Tracking → Devices) or via POST /devices/{uuid}/revoke cuts the integration off.
API keys, OAuth apps and SSO aren’t available yet. Integrations sign in as a dedicated user whose permissions you control.
curl -s -X POST "$BASE/auth/refresh" \
-H "Content-Type: application/json" \
-d '{"refresh":"'"$REFRESH"'","device_uuid":"'"$DEVICE_UUID"'"}'
# → new access AND new refresh token. Store both; the old refresh token is now invalid.Access is deny-by-default. Each endpoint requires a capability; workspace admins hold all of them. Finance fields are stripped from asset writes without finance.
| Capability | Grants |
|---|---|
view | Read assets, locations, movements, stock-takes, reports and CSV exports |
create | Create assets |
edit | Update assets, status, tags, movements, check-outs, documents, contracts, work orders |
delete | Delete assets, tags, org records and PM schedules |
manage | Locations, categories, custodians, departments, custom fields, PM schedules |
finance | Cost fields, disposals, depreciation runs and profiles |
audit | Create, open, scan, reconcile and close stock-takes |
devices | Readers, zones, tag orders and stock, NFC links |
alerts | Alert rules; acknowledge and resolve alerts |
What you can build on
103 documented endpoints in 10 groups. Open the full reference →
Sign in with a user’s email and password to receive a short-lived access token and a rotating refresh token, bound to a device session you can revoke.
/login/auth/refresh/meThe asset register. Finance fields are stripped from writes unless the caller holds finance; status changes go through their own endpoint.
/assets/assets/lookup/assets/{id}Record where assets go and who has them. A movement updates the asset’s location/custodian and appends to its history.
/movements/movements/assets/{id}/movementsGeneric CRUD for the location hierarchy and organisation records: properties, buildings, floors, locations, categories, custodians and departments.
/{collection}/{collection}/{collection}/{id}Assign QR, barcode, NFC and UHF RFID tags. Duplicate active codes/EPCs are rejected; RFID EPCs are checked against tag stock when provisioning is in use.
/assets/{id}/tags/tags/tags/{id}Scoped count sessions. Scans are append-only; reconcile writes found / missing / misplaced / unregistered / duplicate variances.
/stocktake/sessions/stocktake/sessions/stocktake/sessions/{id}Work orders, PM schedules, calibration, attachments and warranty/service/insurance contracts. “Due” lists are pull queries — nothing is auto-generated or pushed.
/work-orders/work-orders/{id}/work-ordersMonthly runs per book. Compute a draft (no asset changes), then post. Posting a tax-book run never changes register NBV.
/depreciation/runs/depreciation/runs/depreciation/runs/{id}JSON reports and CSV exports. CSV cells beginning with = + - @ are neutralised.
/reports/register/reports/by-location/reports/by-custodianReaders are a separate principal from users. Creating a reader (or rotating its secret) returns the secret once. Alerts are evaluated server-side from authenticated reader events.
/readers/readers/readers/{id}Signed alert-rule webhooks
Webhooks are delivered by alert rules. Create a rule with the webhook channel and an https URL; when the rule fires from an authenticated reader event, Asetavo POSTs the alert as JSON. Conditions:
left_zone— a tagged asset leaves the rule’s zoneentered_zone— a tagged asset enters itunregistered— a reader sees a tag that isn’t in the registercheckout_exit— a checked-out asset passes a readerreader_silent— a reader stops reporting
The body is signed with the rule’s webhook secret (shown once when the rule is created): X-Asetavo-Signature = hex(HMAC-SHA256(secret, raw body)). Delivery requires https to a public host, follows no redirects, uses a short timeout and retries a few times with back-off. Respond 2xx quickly and process asynchronously.
GET /movements or GET /assets/{id}/history.POST https://your-endpoint.example.com/asetavo-alerts
Content-Type: application/json
X-Asetavo-Event: asset_manager.alert
X-Asetavo-Signature: 5d41402abc4b2a76b9719d911017c592… (hex HMAC-SHA256 of the raw body)
{
"alert_id": 311,
"rule_id": 4,
"rule_name": "Guest TVs stay on guest floors",
"condition": "left_zone",
"type": "left_zone",
"message": "…",
"asset_id": 118,
"asset_tag": "AST-000118",
"reader_id": 3,
"zone_id": 2,
"event_id": 90211,
"direction": "out",
"observed_at": "2026-09-27 14:02:11",
"fired_at": "2026-09-27 14:02:12"
}import crypto from 'node:crypto';
import express from 'express';
const app = express();
// Verify against the RAW body — re-serialised JSON will not match.
app.post('/asetavo-alerts', express.raw({ type: 'application/json' }), (req, res) => {
const expected = crypto
.createHmac('sha256', process.env.ASETAVO_WEBHOOK_SECRET)
.update(req.body) // Buffer of the raw body
.digest('hex');
const given = String(req.get('X-Asetavo-Signature') || '');
const ok = given.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
const alert = JSON.parse(req.body.toString('utf8'));
// …queue it; respond fast
res.sendStatus(204);
});Send RFID reads, signed
Fixed readers and gateways post batches of tag reads to POST /ingest/events. A reader is its own principal: it authenticates with the secret shown once when it was created (or rotated) — never with a user token — and it can’t call any other endpoint.
Body: {"events": [{ "epc" | "code", "direction": "in|out|present", "rssi", "observed_at" }]}. Reads resolve to assets by EPC or code, take the reader’s zone, and are evaluated against your alert rules.
import hashlib, hmac, json, os, secrets, time, requests
BASE = "https://app.asetavo.com/api/w/<your-workspace>"
READER_ID = "3" # the reader's numeric id
SECRET = os.environ["READER_SECRET"].encode() # shown once when the reader was created
body = json.dumps({"events": [
{"epc": "E28011700000020A1B2C3D4E", "direction": "out", "rssi": -58,
"observed_at": "2026-09-27T14:02:11Z"}
]}, separators=(",", ":")).encode()
ts = str(int(time.time())) # must be within ±60 s of server time
nonce = secrets.token_hex(16) # single-use per reader
signing_input = READER_ID + ts + nonce + hashlib.sha256(body).hexdigest()
sig = hmac.new(SECRET, signing_input.encode(), hashlib.sha256).hexdigest()
r = requests.post(f"{BASE}/ingest/events", data=body, headers={
"Content-Type": "application/json",
"X-Device-Id": READER_ID, "X-Timestamp": ts,
"X-Nonce": nonce, "X-Signature": sig,
})
print(r.status_code, r.json())Envelope, errors & limits
Check the HTTP status, then success. On failure, message says why.
{
"success": true | false,
"message": "OK",
"data": { … } | [ … ],
"meta": { "page", "per_page", "total", "total_pages" }
}HTTP/1.1 403 Forbidden
{ "success": false, "message": "Forbidden", "data": [] }| Status | Meaning |
|---|---|
200 / 201 | Success (201 for creates and ingest) |
400 | Missing or invalid input |
401 | No, expired or invalid token — refresh and retry once |
403 | Signed in, but missing the capability for this endpoint |
404 | Not found |
409 | Conflict — e.g. a replayed reader nonce |
413 | Reader batch too large |
422 | Validation failed (message explains why) |
429 | Throttled — back off and retry later |
| Limit | Value |
|---|---|
| Access token lifetime | Up to 12 hours (expires_in, in seconds) |
| Refresh token | Up to 30 days; rotated on every refresh |
| Sign-in throttling | 5 failed attempts → temporary lock-out that doubles each time (30 s up to 1 h); HTTP 429 |
| Page size | per_page default 25, maximum 200 |
| Reader ingest body | ≤ 256 KB and ≤ 500 events per request |
| Reader ingest rate | 600 events per minute per reader; HTTP 429 above that |
| Reader clock skew | ±60 seconds; nonces are single-use (HTTP 409 on replay) |
| Attachments | ≤ 10 MB; PDF, images and Office documents |
Other endpoints don’t publish a request quota yet. Keep polling modest and back off on 429.
SDKs: not yet. Any HTTP client works.
There are no official SDKs. The API is plain JSON over HTTPS, so curl, fetch, requests or your platform’s HTTP client is all you need. Common patterns:
Page through GET /assets (or pull /export/register.csv) into your data warehouse or ERP on a schedule.
After finance posts a run, read /depreciation/runs/{id} and create the journal in your accounting system.
Point an alert-rule webhook at a small relay that verifies the signature and forwards to your ticketing or chat tool.
If your fixed readers can’t send HTTPS themselves, your own middleware can batch their tag reads and post them, signed, to /ingest/events.
Your HR or IT workflow records check-outs for new starters and returns (/checkins) when people leave.
Resolve any QR, barcode or EPC with /assets/lookup?tag= to build your own scanning screen.
Ready to build?
Tell us what you want to connect. We’ll enable API access for your workspace and send your base URL.
Business plan and above · user tokens · signed webhooks
