API and webhooks
Use the API to pull your repairs, customers and invoices into a spreadsheet, Zapier or your own system, or to make repairs from your website. Webhooks tell your system the moment a repair is made, changes status, or a payment comes in.
Keys
Make a key in Settings → Developers. It's shown once, so copy it somewhere safe. A read key can only read; a write key can also make repairs. Send it on every request:
curl https://app.nomadtechrepairs.com/api/v1/tickets?limit=10 \ -H "Authorization: Bearer nrk_your_key_here"
A key only ever sees your shop. Remove it in Settings and it stops working straight away. Requests are limited to 600 every 5 minutes from one address. Answers are JSON; times are UTC (ISO 8601) and money is in your shop's currency.
Endpoints
| Request | What you get |
|---|---|
GET /api/v1/me | Your shop's name, country, currency and the key's scope. |
GET /api/v1/tickets | Repairs, most recently changed first. ?updatedSince=2026-10-01T00:00:00Z for only what changed, ?status=Ready, ?limit= (up to 500). |
GET /api/v1/tickets/{id} | One repair with the customer, its history and its estimates and invoices. |
POST /api/v1/tickets | Make a repair (write keys). See below. |
GET /api/v1/customers | Customers, newest first. ?q= searches name, phone and email; ?createdSince=, ?limit=. |
GET /api/v1/customers/{id} | One customer and their repairs. |
GET /api/v1/invoices | Invoices with lines, totals and what's been paid. ?status=Paid, ?from=2026-10-01, ?limit=. |
Lists come back as { "data": [ ... ] }. Repair statuses are New, Diagnosing, WaitingOnParts, InProgress, Ready, PickedUp and Cancelled.
Making a repair
curl -X POST https://app.nomadtechrepairs.com/api/v1/tickets \
-H "Authorization: Bearer nrk_your_write_key" -H "Content-Type: application/json" \
-d '{ "customer": { "name": "Sam Lee", "phone": "555-555-0100", "email": "sam@example.com" },
"deviceType": "Phone", "brand": "Apple", "model": "iPhone 13",
"issue": "Cracked screen", "notes": "Booked from our website" }'
Send customerId instead of customer to use someone already in your shop; otherwise they're matched by phone or email, or added. deviceType is optional (it's guessed from the brand and model). You get back 201 with the new repair's id and number.
Errors
400 something in the request isn't right (the message says what), 401 no key or a removed key, 402 the shop isn't on Pro, 403 a read key tried to write, 404 not found (or not your shop's), 429 too many requests. Errors look like { "error": "…" }.
Webhooks
Add your address in Settings → Developers → Webhooks (https only) and pick what to be told about:
| Event | When |
|---|---|
ticket.created | A repair is checked in (by anyone, or through the API). |
ticket.status_changed | A repair moves to a new status. previousStatus says where it was. |
payment.received | A payment, deposit or refund (a negative amount, with refund: true). |
customer.created | A new customer is added (the customer form, a check-in, the kiosk or the API; not imports). Sends their id, name, isBusiness and textConsent. |
Each one is a POST with a JSON body like this, plus the headers X-Nomad-Event, X-Nomad-Delivery (an id) and X-Nomad-Signature:
{
"event": "ticket.status_changed",
"createdAt": "2026-10-15T14:02:11Z",
"data": { "ticket": { "id": 812, "number": 1042, "display": "R-1042", "status": "Ready",
"previousStatus": "InProgress", "customerId": 377, "deviceType": "Phone",
"brand": "Apple", "model": "iPhone 13", "issue": "Cracked screen" } }
}
Customer phone numbers and emails aren't sent; fetch the customer with your key if you need them. Answer with any 2xx within 8 seconds. Failed deliveries are tried again after 1 minute, 5 minutes, 30 minutes, 2, 6, 12 and 24 hours, and a webhook that fails 50 times in a row is turned off (turn it back on in Settings). The Test button sends a ping, and Log shows the last 50 deliveries.
Checking the signature
X-Nomad-Signature looks like t=1760537000,v1=5f2c…. v1 is the HMAC-SHA256, in hex, of the time, a dot and the raw body, using the secret you got when you made the webhook. Check it, and that t is recent, before trusting a delivery. In Node:
import crypto from 'node:crypto'
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')))
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300
return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ''))
}
Questions? Use Help in the app and it comes straight to us.