NOF/ API Reference
v1

Authentication

The NOF REST API uses API keys for authentication. Generate a key in your dashboard under Settings → API Keys. Keys are shown only once at creation.

Include your key in every request as a Bearer token:

curl https://your-domain/api/v1/targets \
  -H "Authorization: Bearer nof_a1b2c3d4_<your-secret>"
Note: API keys grant full access to your account data. Treat them like passwords. Never commit them to version control.

Rate Limiting

The API enforces 120 requests per minute per API key, independent from the web app session limits.

When you exceed the limit, the API returns 429 Too Many Requests. Check the RateLimit-* response headers:

HTTP/2 429
RateLimit-Limit: 120
RateLimit-Remaining: 0
RateLimit-Reset: 1700000060

{ "error": "Too many requests — API rate limit exceeded (120/min)" }

Targets

Monitor websites, servers, game servers, and network hosts.

Target types

websiteHTTP/HTTPS endpoint check
serverSSH server (credentials required)
gameMinecraft, FiveM, SA-MP
networkICMP ping or TCP port check

Notification Channels

Teams

Webhooks & HMAC Signing

When you configure a custom webhook channel with a signing secret, NOF signs every outbound webhook payload with HMAC-SHA256 and includes the signature in the X-NOF-Signature header.

Payload shape

{
  "target_id":   "uuid",
  "target_name": "My Website",
  "target_type": "website",
  "event_type":  "down",        // "up" | "down" | "ssl_expiring"
  "old_status":  "up",
  "new_status":  "down",
  "error":       "HTTP 503 Service Unavailable",
  "timestamp":   "2025-01-01T00:00:00.000Z",
  "days_remaining": null        // only set for ssl_expiring events
}

Verifying the signature

// Node.js example
const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const payload = JSON.stringify(req.body);
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  const received = req.headers['x-nof-signature'];
  const valid = crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(received)
  );

  if (!valid) return res.status(401).send('Invalid signature');
  // process event...
});

Error Responses

All error responses use a consistent JSON shape:

{ "error": "Human-readable error message" }
CodeMeaning
200Success
201Created
400Bad Request — invalid or missing parameters
401Unauthorized — missing or invalid API key
403Forbidden — insufficient permissions
404Not Found — resource does not exist
409Conflict — duplicate value (e.g. slug already taken)
410Gone — invitation expired or already used
429Too Many Requests — rate limit exceeded
500Internal Server Error