Developers

Build on naasflow

A read-only REST API and signed webhooks let your own systems stay in sync with your naasflow workspace — employees, leave, payroll, hiring, and billing. Authenticate with a scoped API key from Settings → Integrations.

Authentication

Create an API key under Settings → Integrations → API keys. The key (nf_live_…) is shown once. Send it as a bearer token on every request:

curl https://app.naasflow.com/api/v1/employees \
  -H "Authorization: Bearer nf_live_your_key_here"

Each key carries one or more scopes. Requests are rate limited to 120 requests/minute per key. Available scopes:

employees:reademployees:writedepartments:readdepartments:writeleave:readpayroll:readrecruiting:readevents:read

Endpoints

MethodPathScope
GET/api/v1/employeesList employeesemployees:read
POST/api/v1/employeesCreate an employeeemployees:write
GET/api/v1/employees/{id}Get one employeeemployees:read
PATCH/api/v1/employees/{id}Update an employeeemployees:write
GET/api/v1/departmentsList departmentsdepartments:read
POST/api/v1/departmentsCreate a departmentdepartments:write
GET/api/v1/designationsList designationsdepartments:read
POST/api/v1/designationsCreate a designationdepartments:write
GET/api/v1/leave-requestsList leave requestsleave:read
GET/api/v1/payroll/runsList payroll runspayroll:read
GET/api/v1/payroll/runs/{id}Get one payroll runpayroll:read
GET/api/v1/eventsList emitted eventsevents:read

Pagination & responses

List endpoints accept ?limit= (1–100, default 50) and an opaque ?cursor=. Responses are wrapped in a stable envelope:

{
  "data": [ /* … */ ],
  "pagination": { "next_cursor": "eyJ…", "has_more": true }
}

Errors use a consistent shape with the appropriate HTTP status:

{ "error": { "code": "forbidden", "message": "This key is missing the 'payroll:read' scope." } }

Webhooks

Register HTTPS endpoints under Settings → Integrations → Webhooks and subscribe to events. naasflow POSTs a signed JSON envelope and retries failures with exponential backoff. Subscribable events include:

employee.createdemployee.updatedemployee.terminatedleave.requestedleave.approvedleave.rejectedpayroll.run.finalizedpayroll.payment.exportedpayroll.payment.settledattendance.flaggedcandidate.createdapplication.stage_changedoffer.signedcandidate.hiredcontractor.invoice.submittedinvoice.paidsubscription.changed

Verify the X-Naasflow-Signature header before trusting a payload. The signed value is {timestamp}.{rawBody}, HMAC-SHA256 with your endpoint's signing secret:

// Node.js
import crypto from "node:crypto";

function verify(req, secret) {
  const ts = req.headers["x-naasflow-timestamp"];
  const sig = req.headers["x-naasflow-signature"]; // "v1=<hex>"
  const expected =
    "v1=" + crypto.createHmac("sha256", secret)
      .update(ts + "." + req.rawBody)
      .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
}
# Python
import hmac, hashlib

def verify(headers, raw_body, secret):
    ts = headers["X-Naasflow-Timestamp"]
    sig = headers["X-Naasflow-Signature"]  # "v1=<hex>"
    expected = "v1=" + hmac.new(
        secret.encode(), f"{ts}.{raw_body}".encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(sig, expected)