API reference

Drive NorminUp from your own tools: services, incidents, maintenance, status pages, team.

Base addresshttps://api.norminup.com

Getting started

Authentication

The API is included in the Pro plan. Create a key on the Developers page of the dashboard, then send it in the Authorization header of every request. A key acts on behalf of your organization: keep it server-side, never in visible code.

Responses

Every response is JSON. A list returns count and data, one item data, and a creation answers 201 with the new ID.

Rate limit

100 requests per minute per address. Beyond that, the response is 429.

API guide in the documentation
Authentication
curl https://api.norminup.com/api/v2/services \
  -H "Authorization: Bearer YOUR_KEY"

Errors

400
Invalid request: an error field, or errors with details field by field.
401
Missing or invalid key.
403
The plan does not include the API, or a plan limit is reached.
404
The item does not exist, or not in your organization.
429
Too many requests.

Account and usage

GET/api/v2/usage

Your plan and what you use, with the limits.

Request
curl -X GET https://api.norminup.com/api/v2/usage \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "plan": "PRO",
  "usage": {
    "services": { "current": 12, "limit": 50, "remaining": 38 },
    "team_members": { "current": 3, "limit": 10, … },
    …
  }
}
GET/api/v2/stats

Organization figures: services by state, incidents.

Request
curl -X GET https://api.norminup.com/api/v2/stats \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
GET/api/v2/billing/portal

An address to the Stripe billing portal, to manage the subscription.

Request
curl -X GET https://api.norminup.com/api/v2/billing/portal \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "url": "https://billing.stripe.com/p/session/…"
}
GET/api/v2/organization

The organization’s details.

Request
curl -X GET https://api.norminup.com/api/v2/organization \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": {
    "id": 1,
    "name": "Boulangerie Martin",
    "slug": "boulangerie-martin",
    "plan": "PRO",
    "created_at": "2026-09-14T08:12:00.000Z",
    "custom_domain": "status.boulangerie-martin.fr",
    "domain_verified": 1
  }
}
PUT/api/v2/organization

Rename the organization.

Request
curl -X PUT https://api.norminup.com/api/v2/organization \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Boulangerie Martin"
  }'
Response200
{
  "message": "Organization updated"
}
GET/api/v2/organization/audit-logs

The activity log, page by page.

Address parameters

limit = 50
Number of entries, 200 at most
page = 1
Page to read
user_id
Actions of one member
action
Kind of action
entity_type
Kind of item affected
Request
curl -X GET https://api.norminup.com/api/v2/organization/audit-logs \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": [ { … } ],
  …
}

Services

GET/api/v2/services

All your services, with their state.

Request
curl -X GET https://api.norminup.com/api/v2/services \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 1,
  "data": [
    {
      "id": 42,
      "name": "Site de la boutique",
      "description": null,
      "type": "http",
      "status": "operational",
      "is_maintenance": 0,
      "group_name": "Site",
      "created_at": "2026-10-01T09:00:00.000Z",
      "updated_at": "2026-10-17T21:04:00.000Z"
    }
  ]
}
GET/api/v2/services/:id

One service.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
POST/api/v2/services

Create a service. Accepted types: web, api, tcp, udp, icmp (or ping), dns, ssl, domain, heartbeat, database, redis, docker, websocket, smtp, imap, pop3, grpc, payment_gateway, discord_shards and other; http is still accepted as a synonym of web. url is required for web, api and http.

Request
curl -X POST https://api.norminup.com/api/v2/services \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Site de la boutique",
    "type": "web",
    "url": "https://www.example.com"
  }'
Response201
{
  "message": "Service created",
  "data": { "id": 42 }
}
PUT/api/v2/services/:id

Change a service, with the same fields as creation.

Request
curl -X PUT https://api.norminup.com/api/v2/services/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Site de la boutique",
    "type": "web",
    "url": "https://www.example.com"
  }'
Response200
{
  "message": "Service updated"
}
DELETE/api/v2/services/:id

Delete a service.

Request
curl -X DELETE https://api.norminup.com/api/v2/services/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Service deleted"
}
GET/api/v2/services/:id/metrics

A service’s measurements over a period: response time, availability.

Address parameters

range
Period to cover
Request
curl -X GET https://api.norminup.com/api/v2/services/:id/metrics \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
GET/api/v2/services/:id/uptime-history

Day-by-day availability history.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id/uptime-history \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
GET/api/v2/services/:id/logs

The service’s latest checks.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id/logs \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/services/test

Test an address without creating a service, with the same fields as creation. Private and internal addresses are refused just like for a real service.

Request
curl -X POST https://api.norminup.com/api/v2/services/test \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "web",
    "url": "https://www.example.com"
  }'
Response201
{
  "data": {
    "status": "operational",
    "responseTime": 182,
    …
  }
}
GET/api/v2/services/:id/shards

The state of each shard of a Discord bot (discord_shards type). data is null until the bot has reported.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id/shards \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": {
    "status": "operational",
    "total": 2,
    "ready": 2,
    "starting": 0,
    "missingCount": 0,
    "shards": [
      { "id": 0, "state": "ready", "status": "ready", "ping_ms": 41, "guilds": 812, "last_seen": "2026-10-17T21:04:00.000Z" },
      …
    ]
  }
}
GET/api/v2/services/:id/heartbeat-token

The token of a heartbeat or discord_shards service. A scheduled job reports that it ran by calling /api/heartbeat/t/<token>; a sharded bot sends it to /api/heartbeat/shards.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id/heartbeat-token \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { "token": "9f2c…" }
}
GET/api/v2/services/:id/maintenance

A service’s maintenance windows.

Request
curl -X GET https://api.norminup.com/api/v2/services/:id/maintenance \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/services/:id/maintenance

Put a service under maintenance for a period. ISO 8601 dates.

Request
curl -X POST https://api.norminup.com/api/v2/services/:id/maintenance \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Mise à jour de la base",
    "description": "Coupure de quelques minutes.",
    "start_time": "2026-11-02T03:00:00Z",
    "end_time": "2026-11-02T04:00:00Z"
  }'
Response201
{
  "message": "Maintenance scheduled",
  "data": { "id": 8 }
}
DELETE/api/v2/services/:id/maintenance/:maintenanceId

Cancel a service’s maintenance.

Request
curl -X DELETE https://api.norminup.com/api/v2/services/:id/maintenance/:maintenanceId \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Maintenance deleted"
}
GET/api/v2/service-groups

Service groups.

Request
curl -X GET https://api.norminup.com/api/v2/service-groups \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/service-groups

Create a service group.

Request
curl -X POST https://api.norminup.com/api/v2/service-groups \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Site"
  }'
Response201
{
  "message": "Service group created",
  "data": { "id": 4, "name": "Site" }
}
PUT/api/v2/service-groups/:id

Rename a group.

Request
curl -X PUT https://api.norminup.com/api/v2/service-groups/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Boutique en ligne"
  }'
Response200
{
  "message": "Service group updated"
}
DELETE/api/v2/service-groups/:id

Delete a group. Its services are not deleted.

Request
curl -X DELETE https://api.norminup.com/api/v2/service-groups/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Service group deleted"
}

Incidents

GET/api/v2/incidents

Incidents, newest first.

Address parameters

status
Keep one stage only
limit = 20
Number of incidents
Request
curl -X GET https://api.norminup.com/api/v2/incidents \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
GET/api/v2/incidents/:id

One incident and the affected services.

Request
curl -X GET https://api.norminup.com/api/v2/incidents/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
POST/api/v2/incidents

Create an incident.

Body fields

service_id
Affected service
title
Title shown on the status page
status
investigating, identified, monitoring or resolved
description
First update message, optional
impact
none, minor, major or critical; none by default
Request
curl -X POST https://api.norminup.com/api/v2/incidents \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": 42,
    "title": "Commandes en ligne indisponibles",
    "status": "investigating",
    "description": "Le site ne prend plus les commandes, on regarde.",
    "impact": "major"
  }'
Response201
{
  "message": "Incident created",
  "id": 12
}
PUT/api/v2/incidents/:id

Change an incident’s title or stage; description adds an update message. The impact is set at creation.

Request
curl -X PUT https://api.norminup.com/api/v2/incidents/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Commandes en ligne indisponibles",
    "status": "identified",
    "description": "Cause trouvée, correctif en cours."
  }'
Response200
{
  "message": "Incident updated"
}
POST/api/v2/incidents/:id/updates

Add an update shown on the status page, and change stage if needed.

Request
curl -X POST https://api.norminup.com/api/v2/incidents/:id/updates \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "La cause est trouvée, la correction est en cours.",
    "status": "identified"
  }'
Response201
{
  "message": "Update posted",
  "status": "identified"
}
DELETE/api/v2/incidents/:id

Delete an incident.

Request
curl -X DELETE https://api.norminup.com/api/v2/incidents/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Incident deleted"
}

Maintenance

GET/api/v2/maintenance

Scheduled maintenance.

Request
curl -X GET https://api.norminup.com/api/v2/maintenance \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
GET/api/v2/maintenance/:id

One maintenance.

Request
curl -X GET https://api.norminup.com/api/v2/maintenance/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
POST/api/v2/maintenance

Schedule maintenance. ISO 8601 dates.

Request
curl -X POST https://api.norminup.com/api/v2/maintenance \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Mise à jour du serveur",
    "start_time": "2026-11-02T03:00:00Z",
    "end_time": "2026-11-02T04:00:00Z"
  }'
Response201
{
  "message": "Maintenance scheduled",
  "id": 5
}
PUT/api/v2/maintenance/:id

Change maintenance, with the same fields as creation.

Request
curl -X PUT https://api.norminup.com/api/v2/maintenance/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Mise à jour du serveur",
    "start_time": "2026-11-02T03:00:00Z",
    "end_time": "2026-11-02T05:00:00Z"
  }'
Response200
{
  "message": "Maintenance updated"
}
DELETE/api/v2/maintenance/:id

Delete maintenance.

Request
curl -X DELETE https://api.norminup.com/api/v2/maintenance/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Maintenance deleted"
}

Status pages

GET/api/v2/pages

Your status pages.

Request
curl -X GET https://api.norminup.com/api/v2/pages \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
GET/api/v2/pages/:id

One status page.

Request
curl -X GET https://api.norminup.com/api/v2/pages/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
POST/api/v2/pages

Create a page. The short address (slug) only holds lowercase letters, digits and hyphens.

Request
curl -X POST https://api.norminup.com/api/v2/pages \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Boulangerie Martin",
    "slug": "boulangerie-martin",
    "title": "État de nos services"
  }'
Response201
{
  "message": "Status page created",
  "id": 3,
  "slug": "boulangerie-martin"
}
PUT/api/v2/pages/:id

Change a page. Possible fields: name, slug, title, description, logo_url, domain, config.

Request
curl -X PUT https://api.norminup.com/api/v2/pages/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Boulangerie Martin",
    "slug": "boulangerie-martin",
    "description": "Commandes, site et caisse."
  }'
Response200
{
  "message": "Page updated"
}
DELETE/api/v2/pages/:id

Delete a page.

Request
curl -X DELETE https://api.norminup.com/api/v2/pages/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Page deleted"
}

Incident templates

GET/api/v2/templates

Your incident templates.

Request
curl -X GET https://api.norminup.com/api/v2/templates \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
GET/api/v2/templates/:id

One template.

Request
curl -X GET https://api.norminup.com/api/v2/templates/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "data": { … }
}
POST/api/v2/templates

Create a template.

Request
curl -X POST https://api.norminup.com/api/v2/templates \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Panne du site",
    "title": "Site momentanément indisponible"
  }'
Response201
{
  "message": "Template created",
  "id": 7
}
PUT/api/v2/templates/:id

Change a template.

Request
curl -X PUT https://api.norminup.com/api/v2/templates/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Panne du site",
    "title": "Site indisponible, nous enquêtons"
  }'
Response200
{
  "message": "Template updated"
}
DELETE/api/v2/templates/:id

Delete a template.

Request
curl -X DELETE https://api.norminup.com/api/v2/templates/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Template deleted"
}

Webhooks

GET/api/v2/webhooks

Your webhooks.

Request
curl -X GET https://api.norminup.com/api/v2/webhooks \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/webhooks

Create a webhook. “*” receives every event.

Request
curl -X POST https://api.norminup.com/api/v2/webhooks \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/norminup",
    "events": ["*"]
  }'
Response201
{
  "id": 9,
  "name": null,
  "url": "https://example.com/norminup",
  "events": "[\"*\"]",
  "is_active": true
}
PUT/api/v2/webhooks/:id

Change a webhook, with the same fields as creation.

Request
curl -X PUT https://api.norminup.com/api/v2/webhooks/:id \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/norminup",
    "events": ["service.down"]
  }'
Response200
{
  "message": "Webhook updated"
}
DELETE/api/v2/webhooks/:id

Delete a webhook.

Request
curl -X DELETE https://api.norminup.com/api/v2/webhooks/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Webhook deleted"
}
GET/api/v2/webhooks/:id/deliveries

The last 50 deliveries, with the response received.

Request
curl -X GET https://api.norminup.com/api/v2/webhooks/:id/deliveries \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/webhooks/:id/test

Send a test event to the webhook. It is queued: its result shows up in the deliveries.

Request
curl -X POST https://api.norminup.com/api/v2/webhooks/:id/test \
  -H "Authorization: Bearer YOUR_KEY"
Response201
{
  "data": {
    "success": true,
    "message": "Test event queued"
  }
}
POST/api/v2/webhooks/:id/pause

Pause a webhook: nothing is sent until it is resumed.

Request
curl -X POST https://api.norminup.com/api/v2/webhooks/:id/pause \
  -H "Authorization: Bearer YOUR_KEY"
Response201
{
  "message": "Webhook paused",
  "data": { "is_active": false }
}
POST/api/v2/webhooks/:id/resume

Resume a paused webhook.

Request
curl -X POST https://api.norminup.com/api/v2/webhooks/:id/resume \
  -H "Authorization: Bearer YOUR_KEY"
Response201
{
  "message": "Webhook resumed",
  "data": { "is_active": true }
}

Subscribers

GET/api/v2/subscribers

Your status pages’ subscribers, page by page.

Address parameters

page = 1
Page to read
limit = 10
Subscribers per page
Request
curl -X GET https://api.norminup.com/api/v2/subscribers \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 10,
  "data": [ { … }, … ],
  "pagination": {
    "total": 48,
    "page": 1,
    "limit": 10,
    "totalPages": 5
  }
}
POST/api/v2/subscribers

Subscribe an address to services. A confirmation email is sent to it: the subscriber stays pending, receives nothing and does not count toward the quota until they click. Services hidden from the public page are skipped.

Body fields

email
Address to subscribe
service_ids
Services, 1 to 20
language
fr or en, language of the email; en by default
Request
curl -X POST https://api.norminup.com/api/v2/subscribers \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "client@example.com",
    "service_ids": [42, 43],
    "language": "fr"
  }'
Response201
{
  "message": "Confirmation email sent",
  "data": {
    "email": "client@example.com",
    "status": "pending_confirmation"
  }
}
POST/api/v2/subscribers/import

Import subscribers from a CSV file sent as multipart/form-data, field file, 2 MB at most. Columns: email, and service_id for one service. Rows past the plan quota, duplicates and invalid rows are listed in errors.

Request
curl -X POST https://api.norminup.com/api/v2/subscribers/import \
  -H "Authorization: Bearer YOUR_KEY"
Response201
{
  "message": "Subscribers imported",
  "data": {
    "message": "Import completed",
    "successCount": 48,
    "errorCount": 2,
    "errors": [ { "row": { … }, "error": "Already subscribed" }, … ]
  }
}
DELETE/api/v2/subscribers/:id

Remove a subscriber.

Request
curl -X DELETE https://api.norminup.com/api/v2/subscribers/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Subscriber removed"
}

Team

GET/api/v2/team/members

The organization’s members.

Request
curl -X GET https://api.norminup.com/api/v2/team/members \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
DELETE/api/v2/team/members/:userId

Remove a member.

Request
curl -X DELETE https://api.norminup.com/api/v2/team/members/:userId \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Team member removed successfully"
}
GET/api/v2/team/invitations

Pending invitations.

Request
curl -X GET https://api.norminup.com/api/v2/team/invitations \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "count": 2,
  "data": [ { … }, { … } ]
}
POST/api/v2/team/invitations

Invite someone by email. Accepted roles: admin, editor or viewer.

Request
curl -X POST https://api.norminup.com/api/v2/team/invitations \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "collegue@example.com",
    "role": "admin"
  }'
Response201
{
  "message": "Invitation created",
  "data": { … }
}
DELETE/api/v2/team/invitations/:id

Cancel an invitation.

Request
curl -X DELETE https://api.norminup.com/api/v2/team/invitations/:id \
  -H "Authorization: Bearer YOUR_KEY"
Response200
{
  "message": "Invitation revoked"
}

API v1

Only one endpoint is left in v1, kept for existing integrations. Use v2 for any new code.

POST/api/v1/incidents