← Back to home

Developer API

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

Base URLhttps://www.notifly.ro/api/v1

The machine-readable OpenAPI 3.1 spec lives at /api/v1/openapi.json — import it into Postman, Insomnia, or a code generator.

Authentication

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>
  • Enterprise only. The API is available on the Enterprise plan with an active (or trialing) subscription.
  • Create and manage keys in Dashboard → Settings → API keys. The full key is shown once at creation — store it securely. We keep only a SHA-256 hash, so we can never show it again.
  • Treat keys as secrets. Revoke a leaked key immediately from the same screen.

Rate limits

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.

HeaderMeaning
X-RateLimit-LimitMax requests allowed in the current window (120).
X-RateLimit-RemainingRequests remaining in the current window.
X-RateLimit-ResetUnix epoch (seconds) when the window resets.
Retry-AfterSeconds to wait before retrying (on 429 only).

Errors

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"] } }
}
CodeHTTPMeaning
unauthorized401Missing/invalid key, or no active Enterprise plan.
rate_limited429Over 120 requests/minute for this key.
validation_failed422Request body failed schema validation.
not_found404Resource not found (or not visible to your org).
invalid_json400Request body was not valid JSON.
no_active_provider422No active provider exists for the org.
invalid_service_type422serviceTypeId does not belong to your org.
invalid_employee422employeeId is unknown or cannot offer the service.

Request IDs

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.

Pagination

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.

Endpoint reference

Replace $NTF_KEY with your key in the examples below.

GET/assets

List assets (latest service record each). Supports cursor, limit, search.

curl https://www.notifly.ro/api/v1/assets \
  -H "Authorization: Bearer $NTF_KEY"
POST/assets

Create 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" }
  }'
GET/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"
PATCH/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" }'
DELETE/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"
GET/appointments

List appointments. Filters: status, from, to. Cursor-paginated.

curl "https://www.notifly.ro/api/v1/appointments?status=CONFIRMED&limit=20" \
  -H "Authorization: Bearer $NTF_KEY"
POST/appointments

Create 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" }
  }'
GET/appointments/{id}

Fetch one appointment.

curl https://www.notifly.ro/api/v1/appointments/<id> \
  -H "Authorization: Bearer $NTF_KEY"
GET/customers

List customers. Supports cursor, limit, search (name / phone / email).

curl "https://www.notifly.ro/api/v1/customers?search=popescu" \
  -H "Authorization: Bearer $NTF_KEY"
GET/service-types

List active service types. Use a serviceTypeId when creating assets/appointments.

curl https://www.notifly.ro/api/v1/service-types \
  -H "Authorization: Bearer $NTF_KEY"
GET/reminders

List 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"
GET/webhooks

List your webhook endpoints (secrets are never returned here).

curl https://www.notifly.ro/api/v1/webhooks \
  -H "Authorization: Bearer $NTF_KEY"
POST/webhooks

Register 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"]
  }'
PATCH/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 }'
DELETE/webhooks/{id}

Delete a webhook endpoint.

curl -X DELETE https://www.notifly.ro/api/v1/webhooks/<id> \
  -H "Authorization: Bearer $NTF_KEY"

Webhooks

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.

Events

  • appointment.created — a new appointment was created (including via the API).
  • reminder.sent — a reminder notification was sent to a customer.

Delivery headers

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

Verifying the signature

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