Developers

Build on Utilisense

A simple JSON API with least-privilege tokens. Connect accounting, CRM, scheduling, payments or messaging apps to Utilisense, then list your integration in the marketplace.

How it works

Each Utilisense customer runs their own server. Your integration talks to that customer's server at https://<their-domain>/ext/v1/, using an API token the customer creates and gives to you. Nothing you build is installed on their server, and they can revoke your access at any moment.

  1. The customer signs in to Utilisense as an administrator and opens /platform/api-tokens/.
  2. They create a token, choose only the permissions your integration needs, and set an expiry.
  3. The token is shown once. They paste it into your product's settings along with their Utilisense address.

Authentication

Send the token as a bearer token on every request. Cookies are ignored, so requests must come from your server — never put a token in browser code.

curl https://app.example.com/ext/v1/ \
  -H "Authorization: Bearer usk_xxxxxxxxxx_..."

The root endpoint returns the API version and what the token can do:

{"api_version": "1", "product": "Utilisense",
 "token": {"name": "Acme Billing", "scopes": ["tickets:read"], "expires_at": "2027-01-31T12:00:00+00:00"}}

Tokens are stored hashed, so a lost token can't be recovered — the customer simply creates a new one. Always use HTTPS.

Permissions (scopes)

ScopeAllows
tickets:readRead tickets
View tickets, their status, schedule, contact and service address.
tickets:writeCreate & update tickets
Open new tickets and add notes to existing tickets.
inventory:readRead inventory
View inventory items and stock levels.

A scope only works while the matching module is enabled on the customer's server; otherwise the API returns 404 module_disabled.

Endpoints

GET/ext/v1/ticketstickets:read

List tickets, oldest first. Optional query parameters: status (open or closed), updated_since (ISO-8601 timestamp), limit (1–100, default 50) and after (see paging).

{"data": [{"id": 412, "number": "TCKT-000412", "subject": "No internet",
   "description": "...", "job_type": "trouble", "job_status": "not_started", "state": "new",
   "priority": "1", "severity": "no_service", "contact_name": "Jane Doe", "phone": "...",
   "service_address": "...", "scheduled_date": null, "time_slot": "", "external_id": "",
   "created_at": "...", "updated_at": "...", "closed_at": null}],
 "next_after": null}
GET/ext/v1/tickets/<id>tickets:read

One ticket, in the same shape, wrapped in {"data": ...}.

POST/ext/v1/ticketstickets:readtickets:write

Create a ticket. JSON body: subject (required), description, job_type (trouble, install or other), severity (non_severe, severe or no_service), priority (0–3), contact_name, phone, service_address, external_id (your own reference). Returns 201.

curl -X POST https://app.example.com/ext/v1/tickets \
  -H "Authorization: Bearer usk_..." -H "Content-Type: application/json" \
  -d '{"subject": "Customer reports outage", "severity": "no_service", "external_id": "CRM-8812"}'
POST/ext/v1/tickets/<id>/notestickets:write

Add a note to a ticket: {"note": "Customer called back; issue resolved."}. Notes are stamped with the time and your token's name.

GET/ext/v1/inventoryinventory:read

Active inventory items with id, name, sku, category, unit, warehouse_qty, vehicle_qty, total_qty and low_stock.

Paging

List endpoints return up to limit rows ordered by id. When more remain, next_after holds the last id — pass it back as ?after= to get the next page. A null value means you've reached the end. To sync changes, store the time you started and pass it as updated_since next run.

Errors & limits

Errors return JSON: {"error": "code", "message": "Human-readable detail"}.

StatusCodeMeaning
400invalid_requestA parameter or JSON field is missing or invalid.
401unauthorizedMissing, wrong, expired or revoked token.
403insufficient_scopeThe token doesn't have the permission this call needs.
404not_found / module_disabledNo such record, or the feature is turned off on that server.
405method_not_allowedWrong HTTP method for the endpoint.
429rate_limitedMore than 120 requests a minute per token, or too many failed sign-ins from your IP.

Request bodies are limited to 64 KB. Responses are never cached. Back off and retry on 429 and 5xx.

Testing in the sandbox

Every listing gets free access to the review sandbox: a private Utilisense server with sample customers, tickets and inventory at https://sandbox.utilsense.com/ext/v1/. Open My integrations, choose Test in sandbox and create a key. It carries exactly the permissions your listing requests and lasts 14 days.

Your recent calls, with their requests, responses and status codes, are shown on the same page. Our reviewers test your integration in the same sandbox, so make sure its main workflow runs there cleanly before you submit.

Getting listed

Create a free community account, then submit your integration with a description, website and the permissions it needs. Our team tests every submission, and every later edit, in the review sandbox before it's published. Listing is free.

Reviewers check that the listing matches what the integration does, that every requested permission is actually used (and nothing more is attempted), that errors and rate limits are handled gracefully, and that there is a clear privacy statement and support contact.

Guidelines: request the fewest permissions possible, explain clearly what you do with customer data, keep tokens encrypted at rest, and provide a way for customers to get support.