Developers

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.

REST + JSONJWT + rotating refresh103+ endpointsSigned webhooksHMAC 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"}'
Your API base URL
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.

Quickstart

From zero to your first call

Every response uses the same envelope, so one small wrapper handles the whole API.

1
Create an integration user

Ask your workspace owner (or us) for a dedicated user with only the capabilities your integration needs.

2
POST /login

Store the access and refresh tokens and the device_uuid. The access token is valid for up to 12 hours.

3
Call with a Bearer token

Send Authorization: Bearer <access>. Read success, message, data and, for lists, meta.

POST /login → 200
{
  "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 }
  }
}
GET /assets → 200
{
  "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 }
}
Authentication

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.

Not available yet

API keys, OAuth apps and SSO aren’t available yet. Integrations sign in as a dedicated user whose permissions you control.

Refresh
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.
Capabilities

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.

CapabilityGrants
viewRead assets, locations, movements, stock-takes, reports and CSV exports
createCreate assets
editUpdate assets, status, tags, movements, check-outs, documents, contracts, work orders
deleteDelete assets, tags, org records and PM schedules
manageLocations, categories, custodians, departments, custom fields, PM schedules
financeCost fields, disposals, depreciation runs and profiles
auditCreate, open, scan, reconcile and close stock-takes
devicesReaders, zones, tag orders and stock, NFC links
alertsAlert rules; acknowledge and resolve alerts
Endpoints

What you can build on

103 documented endpoints in 10 groups. Open the full reference →

Authentication & sessions
6

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.

POST/login
POST/auth/refresh
GET/me
Assets
11

The asset register. Finance fields are stripped from writes unless the caller holds finance; status changes go through their own endpoint.

GET/assets
GET/assets/lookup
GET/assets/{id}
Movements & check-outs
8

Record where assets go and who has them. A movement updates the asset’s location/custodian and appends to its history.

GET/movements
POST/movements
GET/assets/{id}/movements
Locations & organisation
9

Generic CRUD for the location hierarchy and organisation records: properties, buildings, floors, locations, categories, custodians and departments.

GET/{collection}
POST/{collection}
PUT/{collection}/{id}
Tags, tag stock & NFC
14

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.

GET/assets/{id}/tags
POST/tags
DELETE/tags/{id}
Stock-take
10

Scoped count sessions. Scans are append-only; reconcile writes found / missing / misplaced / unregistered / duplicate variances.

GET/stocktake/sessions
POST/stocktake/sessions
GET/stocktake/sessions/{id}
Maintenance & contracts
16

Work orders, PM schedules, calibration, attachments and warranty/service/insurance contracts. “Due” lists are pull queries — nothing is auto-generated or pushed.

GET/work-orders
GET/work-orders/{id}
POST/work-orders
Depreciation
8

Monthly runs per book. Compute a draft (no asset changes), then post. Posting a tax-book run never changes register NBV.

GET/depreciation/runs
POST/depreciation/runs
GET/depreciation/runs/{id}
Reports & exports
8

JSON reports and CSV exports. CSV cells beginning with = + - @ are neutralised.

GET/reports/register
GET/reports/by-location
GET/reports/by-custodian
Readers, zones & alerts
13

Readers 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.

GET/readers
POST/readers
PUT/readers/{id}
Webhooks

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 zone
  • entered_zone — a tagged asset enters it
  • unregistered — a reader sees a tag that isn’t in the register
  • checkout_exit — a checked-out asset passes a reader
  • reader_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.

There are no general-purpose event webhooks (such as “asset created”) yet. For changes outside alert rules, poll the API — e.g. GET /movements or GET /assets/{id}/history.
Delivery
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"
}
Verify the signature
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);
});
Reader ingest

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.

X-Device-Id: <reader id> X-Timestamp: <unix seconds, within ±60 s> X-Nonce: <8–64 hex chars, single-use> X-Signature: hex(HMAC-SHA256(secret, device_id + timestamp + nonce + hex(SHA-256(raw body))))

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.

Sign a batch
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())
Conventions

Envelope, errors & limits

Check the HTTP status, then success. On failure, message says why.

Envelope
{
  "success": true | false,
  "message": "OK",
  "data": { … } | [ … ],
  "meta": { "page", "per_page", "total", "total_pages" }
}
Error
HTTP/1.1 403 Forbidden
{ "success": false, "message": "Forbidden", "data": [] }
StatusMeaning
200 / 201Success (201 for creates and ingest)
400Missing or invalid input
401No, expired or invalid token — refresh and retry once
403Signed in, but missing the capability for this endpoint
404Not found
409Conflict — e.g. a replayed reader nonce
413Reader batch too large
422Validation failed (message explains why)
429Throttled — back off and retry later
LimitValue
Access token lifetimeUp to 12 hours (expires_in, in seconds)
Refresh tokenUp to 30 days; rotated on every refresh
Sign-in throttling5 failed attempts → temporary lock-out that doubles each time (30 s up to 1 h); HTTP 429
Page sizeper_page default 25, maximum 200
Reader ingest body≤ 256 KB and ≤ 500 events per request
Reader ingest rate600 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.

Integration patterns

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:

Nightly register sync

Page through GET /assets (or pull /export/register.csv) into your data warehouse or ERP on a schedule.

Depreciation to your GL

After finance posts a run, read /depreciation/runs/{id} and create the journal in your accounting system.

Alerts into your tools

Point an alert-rule webhook at a small relay that verifies the signature and forwards to your ticketing or chat tool.

Your own reader middleware

If your fixed readers can’t send HTTPS themselves, your own middleware can batch their tag reads and post them, signed, to /ingest/events.

Joiners & leavers

Your HR or IT workflow records check-outs for new starters and returns (/checkins) when people leave.

Scan-to-lookup apps

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