Developer API
A REST API to manage customers, assets, service records, appointments, reminders, and outbound webhooks programmatically. JSON in, JSON out. Access requires an active Enterprise subscription.
https://www.notifly.ro/api/v1The machine-readable OpenAPI 3.1 spec lives at /api/v1/openapi.json — import it into Postman, Insomnia, or a code generator.
Authenticate every request with an API key in the Authorization header using the Bearer scheme. Keys are prefixed with ntf_ and are tied to one organisation.
Authorization: Bearer ntf_<your-key>Each API key is limited to 120 requests per minute. Every response carries the current limit state; when you exceed it you get 429 rate_limited with a Retry-After header.
| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Max requests allowed in the current window (120). |
| X-RateLimit-Remaining | Requests remaining in the current window. |
| X-RateLimit-Reset | Unix epoch (seconds) when the window resets. |
| Retry-After | Seconds to wait before retrying (on 429 only). |
Every error uses the same JSON envelope. Branch on the stable code field, not on the human-readable error message.
{
"error": "Validation failed",
"code": "validation_failed",
"requestId": "req_2c1f8e0a-9b3d-4a7e-bf21-6f0a9d4c2e11",
"details": { "fieldErrors": { "identifier": ["Required"] } }
}| Code | HTTP | Meaning |
|---|---|---|
| unauthorized | 401 | Missing/invalid key, or no active Enterprise plan. |
| rate_limited | 429 | Over 120 requests/minute for this key. |
| validation_failed | 422 | Request body failed schema validation. |
| not_found | 404 | Resource not found (or not visible to your org). |
| invalid_json | 400 | Request body was not valid JSON. |
| no_active_provider | 422 | No active provider exists for the org. |
| invalid_service_type | 422 | serviceTypeId does not belong to your org. |
| invalid_employee | 422 | employeeId is unknown or cannot offer the service. |
Every response (success or error) includes an X-Request-Id header, e.g. req_2c1f8e0a-…. The same id appears in error bodies as requestId. Quote it when contacting support so we can correlate your request in our logs.
List endpoints are cursor-paginated. Pass ?cursor=<id>&limit=<1-100> (default limit 50). Responses wrap the page:
{
"data": [ /* ...items... */ ],
"nextCursor": "ckv9z1a2b3c4d5e6f7g8h9i0",
"hasMore": true
}When hasMore is true, pass nextCursor as the next request's cursor. When it is false, nextCursor is null and you have reached the last page.
Replace $NTF_KEY with your key in the examples below.
/assetsList assets (latest service record each). Supports cursor, limit, search.
curl https://www.notifly.ro/api/v1/assets \
-H "Authorization: Bearer $NTF_KEY"/assetsCreate an asset (+ optional service record), finding-or-creating the customer.
curl -X POST https://www.notifly.ro/api/v1/assets \
-H "Authorization: Bearer $NTF_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifier": "B-123-XYZ",
"assetType": "VEHICLE",
"customer": { "name": "Ion Popescu", "phone": "0712345678" },
"serviceRecord": { "serviceTypeId": "<id>", "expiresAt": "2026-12-01" }
}'/assets/{id}Fetch one asset with up to its 5 most recent service records.
curl https://www.notifly.ro/api/v1/assets/<id> \
-H "Authorization: Bearer $NTF_KEY"/assets/{id}Update identifier / metadata / customer / a specific service record.
curl -X PATCH https://www.notifly.ro/api/v1/assets/<id> \
-H "Authorization: Bearer $NTF_KEY" \
-H "Content-Type: application/json" \
-d '{ "identifier": "B-999-NEW" }'/assets/{id}Soft-delete an asset (sets isActive=false).
curl -X DELETE https://www.notifly.ro/api/v1/assets/<id> \
-H "Authorization: Bearer $NTF_KEY"/appointmentsList appointments. Filters: status, from, to. Cursor-paginated.
curl "https://www.notifly.ro/api/v1/appointments?status=CONFIRMED&limit=20" \
-H "Authorization: Bearer $NTF_KEY"/appointmentsCreate a direct appointment, finding-or-creating the customer.
curl -X POST https://www.notifly.ro/api/v1/appointments \
-H "Authorization: Bearer $NTF_KEY" \
-H "Content-Type: application/json" \
-d '{
"serviceTypeId": "<id>",
"scheduledAt": "2026-07-01T10:00:00Z",
"customer": { "name": "Ana Ionescu", "phone": "0723456789" }
}'/appointments/{id}Fetch one appointment.
curl https://www.notifly.ro/api/v1/appointments/<id> \
-H "Authorization: Bearer $NTF_KEY"/customersList customers. Supports cursor, limit, search (name / phone / email).
curl "https://www.notifly.ro/api/v1/customers?search=popescu" \
-H "Authorization: Bearer $NTF_KEY"/service-typesList active service types. Use a serviceTypeId when creating assets/appointments.
curl https://www.notifly.ro/api/v1/service-types \
-H "Authorization: Bearer $NTF_KEY"/remindersList service records with a due date. Filter: ALL | EXPIRING | EXPIRED | FUTURE.
curl "https://www.notifly.ro/api/v1/reminders?filter=EXPIRING" \
-H "Authorization: Bearer $NTF_KEY"/webhooksList your webhook endpoints (secrets are never returned here).
curl https://www.notifly.ro/api/v1/webhooks \
-H "Authorization: Bearer $NTF_KEY"/webhooksRegister an HTTPS endpoint. The signing secret is returned exactly once.
curl -X POST https://www.notifly.ro/api/v1/webhooks \
-H "Authorization: Bearer $NTF_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/notifly",
"events": ["appointment.created", "reminder.sent"]
}'/webhooks/{id}Update url / events / description / active state.
curl -X PATCH https://www.notifly.ro/api/v1/webhooks/<id> \
-H "Authorization: Bearer $NTF_KEY" \
-H "Content-Type: application/json" \
-d '{ "isActive": false }'/webhooks/{id}Delete a webhook endpoint.
curl -X DELETE https://www.notifly.ro/api/v1/webhooks/<id> \
-H "Authorization: Bearer $NTF_KEY"Register an HTTPS endpoint with POST /webhooks and Notifly will POST a JSON payload to it whenever a subscribed event fires. The signing secret (whsec_…) is returned once in the create response — store it to verify deliveries.
appointment.created — a new appointment was created (including via the API).reminder.sent — a reminder notification was sent to a customer.Notifly-Event — the event name (e.g. appointment.created).Notifly-Delivery — a unique delivery id (use it for idempotency).Notifly-Signature — t=<timestamp>,v1=<hmac>.The signature is an HMAC-SHA256 (hex) over `${t}.${rawBody}` using your endpoint secret. Recompute it over the raw request body, constant-time compare against v1, and reject stale timestamps.
import crypto from 'node:crypto'
// Express example. Read the RAW request body (not the parsed JSON) so the
// bytes you hash exactly match what Notifly signed.
function verifyNotiflySignature(rawBody, headerValue, secret) {
const parts = Object.fromEntries(
headerValue.split(',').map((kv) => kv.split('='))
)
const timestamp = Number(parts.t)
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
// Constant-time compare + reject stale timestamps (5 min tolerance).
const sigOk = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1 ?? '')
)
const fresh = Math.abs(Date.now() / 1000 - timestamp) < 300
return sigOk && fresh
}Full machine-readable reference: OpenAPI 3.1 spec (/api/v1/openapi.json). Questions? contact@notifly.ro