REST API

Authenticate, browse the OpenAPI reference, and call the Noru API from curl, fetch, or CI.

What it is

The REST API exposes the same records you see in the app, controls, evidence, risks, vendors, policies, personnel, and the privacy data map, as JSON over HTTPS. It is the right choice for scripts, CI pipelines, and anything that needs to upload a file; AI clients should use the MCP server instead.

Base URL and reference

WhatWhere
Base URLhttps://api.noru.tech
OpenAPI 3 documentGET https://api.noru.tech/openapi
Interactive referencehttps://api.noru.tech/ — press k to search
Markdown spec for LLM contextGET https://api.noru.tech/llms.txt
Health checkGET https://api.noru.tech/health (no auth)

The public paths above are rate-limited to 60 requests per minute per IP.

Authentication

Every /v1 and /v2 request needs an Authorization: Bearer header. Two credentials are accepted:

CredentialLooks likeHow to get one
API keynoru_ followed by 32 charactersAn admin creates it under SettingsDeveloper. See API keys.
OAuth access tokenIssued by the Noru authorization serverObtained through the MCP OAuth flow; valid for 1 hour

A missing, malformed, expired, or revoked credential returns 401 with a WWW-Authenticate header and this body (the message says which check failed):

{ "error": { "code": "UNAUTHORIZED", "message": "..." } }

A valid credential that lacks the scope for a route returns 403:

{ "error": { "code": "FORBIDDEN", "message": "Insufficient permissions" } }

Resources

PathWhat it coversRead scopeWrite scope
/v1/controlsControls, status, ownership, framework mappingsread:controlswrite:controls
/v1/assetsAsset inventoryread:assetswrite:assets
/v1/evidenceEvidence items, mappings, file upload and downloadread:evidencewrite:evidence
/v1/risksRisk register and treatmentsread:riskswrite:risks
/v1/security-findingsSecurity findingsread:riskswrite:risks
/v1/vendorsVendor register, contacts, evidenceread:vendorswrite:vendors
/v1/policiesPolicies, versions, logsread:policieswrite:policies
/v1/privacyData map, RoPA, processing activities, security measures, fideslang ingestionread:datamapswrite:datamaps
/v1/personnel, /v2/personnelPeople directoryread:personnelwrite:personnel
/v1/ownership-principalsUsers and personnel that can own a recordread:users, read:personnel
/v1/trainingTraining plans, campaigns, acknowledgementsread:personnelwrite:personnel
/v1/mcpThe MCP endpoint, not a REST resourceper toolper tool

The exact operations, parameters, and schemas for each resource are in the OpenAPI document; this page only shows the shape they share.

Privacy data map ingestion

POST /v1/privacy/datamaps accepts a fideslang manifest (.fides/datamap.yml parsed to JSON) and materialises systems, datasets, and processing activities into the data map. It is idempotent on slug: re-pushing identical content is a no-op, a changed manifest creates a new immutable version, and anything the manifest no longer names is archived rather than deleted. It requires write:datamaps and the privacy segment. GET /v1/privacy/ropa/export returns the RoPA register as CSV with read:datamaps. AI inventory manifests have no REST route; push them through the MCP tool ingestAiInventory.

Example: list controls

GET /v1/controls returns the organization's controls, filterable by status, frameworkId, domain, ownerType, ownerRefId, and search. It needs read:controls.

curl -s "https://api.noru.tech/v1/controls?status=in_progress&limit=2" \
  -H "Authorization: Bearer $NORU_API_KEY"
const response = await fetch(
  "https://api.noru.tech/v1/controls?status=in_progress&limit=2",
  { headers: { Authorization: `Bearer ${process.env.NORU_API_KEY}` } },
);

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.code}: ${error.message}`);
}

const { data, pagination } = await response.json();
console.log(pagination.total, "controls;", data.length, "in this page");

A trimmed response:

{
  "data": [
    {
      "id": "sm-01",
      "controlId": "SM-01",
      "name": "Information security policy",
      "status": "in_progress",
      "owner": { "type": "user", "refId": "user_123", "resolvedUserId": "user_123" }
    }
  ],
  "pagination": { "total": 87, "limit": 2, "offset": 0 }
}

GET /v1/controls/sm-01 returns one control. The lowercase id is the canonical identifier; the uppercase controlId is for display, and routes accept either.

Pagination and filters

ParameterRule
limit1 to 100, default 50
offset0 or greater, default 0
Named filters (status, frameworkId, and so on)Exact match against the stored value
searchCase-insensitive substring match on the resource's searchable text

Every list response wraps rows in data and reports total, limit, and offset under pagination. Page until offset + limit reaches total.

Errors

Every error uses the same envelope: an error object with a code and a human-readable message.

HTTPcodeWhen
400BAD_REQUESTValidation failed; the message names the field
401UNAUTHORIZEDNo credential, or it is invalid, expired, or revoked
402PAYMENT_REQUIREDThe organization's billing is not active (see below)
403FORBIDDENThe credential lacks the scope for this route
404NOT_FOUNDNo record with that id in this organization
429RATE_LIMITEDRate limit exceeded; retry after Retry-After seconds
500INTERNAL_ERRORUnhandled failure; retry, then contact support with the timestamp

Rate limits

Authenticated /v1 and /v2 calls are limited to 500 requests per 10 minutes per credential, counted per API key or per OAuth token. Every response carries the current state:

HeaderMeaning
X-RateLimit-LimitRequests allowed in the window (500)
X-RateLimit-RemainingRequests left in the current window
X-RateLimit-ResetSeconds until the window resets
Retry-AfterOnly on 429: seconds to wait

Two keys mean two budgets. If a CI job and an MCP client share one key, a busy build can starve the client. Give each consumer its own key.

Billing gate

/v1/evidence additionally requires the organization's billing to be active. When it is not, those routes answer 402 PAYMENT_REQUIRED until an admin resolves it under SettingsBilling or with the Noru team.

Activity and audit trail

Every authenticated request is recorded in the organization's activity log with source api, the action METHOD /path, and the API key (or OAuth user) as actor. The key's Last Used timestamp is updated at the same time. Writes made through the API are therefore visible in the same history as writes made in the app.

What the REST API does not do

  • It never returns data from an organization other than the credential's.
  • It does not create or delete controls; controls come from the frameworks you enable.
  • File uploads happen only through POST /v1/evidence/upload as multipart/form-data, up to 50 MB. No other route accepts a file.
  • It does not push AI inventory manifests (use MCP ingestAiInventory).
  • It does not stream or send webhooks; poll, or use the activity log.

Last updated on