Developer Docs
Sandbox · API v2PlaygroundGet API keys

Compliance & regulations

Select applicable regulations and run fail-closed validation. Verdict semantics are reserved, see the concept page.

POST/api/v2/compliance/regulations/selectSelect applicable regulations
POST/api/v2/compliance/validateRun compliance validation
GET/api/v2/products/{id}/compliance/espr/fullESPR sector projector
GET/api/v2/products/{id}/compliance/esprESPR summary (tombstone)
POST/api/v2/compliance/export/evidence-packEvidence-pack export
KEY

Applicable regulations for a product context. Regulations are versioned rule packs (CELEX id + semver) with an effective/draft/superseded lifecycle.

Request
POST {{baseUrl}}/api/v2/compliance/regulations/select
Authorization: Bearer {{apiKey}}
Content-Type: application/json

{
  "context": {
    "region": "EU",
    "productCategory": "battery",
    "lifecycleStage": "production",
    "effectiveDate": "2026-07-20T00:00:00Z"
  },
  "minimumPriorityLevel": "HIGH"
}
curl -X POST "$BASE_URL/api/v2/compliance/regulations/select" \
  -H "Authorization: Bearer $NORRUVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "context": {
    "region": "EU",
    "productCategory": "battery",
    "lifecycleStage": "production",
    "effectiveDate": "2026-07-20T00:00:00Z"
  },
  "minimumPriorityLevel": "HIGH"
}'
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;

const res = await fetch(`${baseUrl}/api/v2/compliance/regulations/select`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": `application/json`
  },
  body: JSON.stringify({
    "context": {
      "region": "EU",
      "productCategory": "battery",
      "lifecycleStage": "production",
      "effectiveDate": "2026-07-20T00:00:00Z"
    },
    "minimumPriorityLevel": "HIGH"
  })
});

if (!res.ok) {
  const err = await res.json();      // typed envelope: { error: { code, message, details? } }
  throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const data = await res.json();
import os, uuid, requests

base_url = os.environ["NORRUVA_BASE_URL"]
api_key = os.environ["NORRUVA_API_KEY"]

resp = requests.post(
    f"{base_url}/api/v2/compliance/regulations/select",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": f"application/json"
    },
    json={
      "context": {
        "region": "EU",
        "productCategory": "battery",
        "lifecycleStage": "production",
        "effectiveDate": "2026-07-20T00:00:00Z"
      },
      "minimumPriorityLevel": "HIGH"
    },
)
resp.raise_for_status()   # error body is the typed envelope: {"error": {"code", "message", "details"}}
data = resp.json()
package main

import (
	"bytes"
	"fmt"
	"net/http"
	"os"
)

func main() {
	baseURL := os.Getenv("NORRUVA_BASE_URL")
	apiKey := os.Getenv("NORRUVA_API_KEY")
	url := fmt.Sprintf("%s/api/v2/compliance/regulations/select", baseURL)

	payload := []byte(`{
  "context": {
    "region": "EU",
    "productCategory": "battery",
    "lifecycleStage": "production",
    "effectiveDate": "2026-07-20T00:00:00Z"
  },
  "minimumPriorityLevel": "HIGH"
}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Set("Authorization", "Bearer "+apiKey+"")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Status) // non-2xx bodies use the typed envelope {error:{code,message,details}}
}
Related
KEY

Fail-closed validation for a product: no compliance data scores 0 / Indeterminate with findings naming what is missing, never FullyCompliant. BATTERY PRODUCTS ARE CURRENTLY REFUSED: the governed battery obligation pack is quarantined, so a battery-category product returns 503 COMPLIANCE_EVALUATION_UNAVAILABLE rather than any verdict. Every other category evaluates normally, start on electronics.

Verdict / citation semantics are reserved (B1), see the concept page.
RequestTry it
POST {{baseUrl}}/api/v2/compliance/validate
Authorization: Bearer {{apiKey}}
Content-Type: application/json

{ "productId": "{{productId}}" }
curl -X POST "$BASE_URL/api/v2/compliance/validate" \
  -H "Authorization: Bearer $NORRUVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "productId": "{{productId}}"
}'
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;

const res = await fetch(`${baseUrl}/api/v2/compliance/validate`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": `application/json`
  },
  body: JSON.stringify({
    "productId": "{{productId}}"
  })
});

if (!res.ok) {
  const err = await res.json();      // typed envelope: { error: { code, message, details? } }
  throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const data = await res.json();
import os, uuid, requests

base_url = os.environ["NORRUVA_BASE_URL"]
api_key = os.environ["NORRUVA_API_KEY"]

resp = requests.post(
    f"{base_url}/api/v2/compliance/validate",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": f"application/json"
    },
    json={
      "productId": "{{productId}}"
    },
)
resp.raise_for_status()   # error body is the typed envelope: {"error": {"code", "message", "details"}}
data = resp.json()
package main

import (
	"bytes"
	"fmt"
	"net/http"
	"os"
)

func main() {
	baseURL := os.Getenv("NORRUVA_BASE_URL")
	apiKey := os.Getenv("NORRUVA_API_KEY")
	url := fmt.Sprintf("%s/api/v2/compliance/validate", baseURL)

	payload := []byte(`{
  "productId": "{{productId}}"
}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	req.Header.Set("Authorization", "Bearer "+apiKey+"")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Status) // non-2xx bodies use the typed envelope {error:{code,message,details}}
}
Related
KEY

Score, status and findings sourced from the requirements SSOT (obligation IDs ESPR_ART<N>_<TOPIC>).

Request
GET {{baseUrl}}/api/v2/products/{id}/compliance/espr/full
Authorization: Bearer {{apiKey}}
curl -X GET "$BASE_URL/api/v2/products/{id}/compliance/espr/full" \
  -H "Authorization: Bearer $NORRUVA_API_KEY"
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;
const id = "…";

const res = await fetch(`${baseUrl}/api/v2/products/${id}/compliance/espr/full`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${apiKey}`
  }
});

if (!res.ok) {
  const err = await res.json();      // typed envelope: { error: { code, message, details? } }
  throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const data = await res.json();
import os, uuid, requests

base_url = os.environ["NORRUVA_BASE_URL"]
api_key = os.environ["NORRUVA_API_KEY"]
id = "…"

resp = requests.get(
    f"{base_url}/api/v2/products/{id}/compliance/espr/full",
    headers={
        "Authorization": f"Bearer {api_key}"
    },
)
resp.raise_for_status()   # error body is the typed envelope: {"error": {"code", "message", "details"}}
data = resp.json()
package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	baseURL := os.Getenv("NORRUVA_BASE_URL")
	apiKey := os.Getenv("NORRUVA_API_KEY")
	id := "…"
	url := fmt.Sprintf("%s/api/v2/products/%s/compliance/espr/full", baseURL, id)

	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+apiKey+"")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Status) // non-2xx bodies use the typed envelope {error:{code,message,details}}
}
Related
KEY

Deliberate 410 Gone, sector-specific summaries were removed in the regulation-agnostic consolidation. Use POST /api/v2/compliance/validate.

Request
GET {{baseUrl}}/api/v2/products/{id}/compliance/espr
Authorization: Bearer {{apiKey}}
curl -X GET "$BASE_URL/api/v2/products/{id}/compliance/espr" \
  -H "Authorization: Bearer $NORRUVA_API_KEY"
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;
const id = "…";

const res = await fetch(`${baseUrl}/api/v2/products/${id}/compliance/espr`, {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${apiKey}`
  }
});

if (!res.ok) {
  const err = await res.json();      // typed envelope: { error: { code, message, details? } }
  throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const data = await res.json();
import os, uuid, requests

base_url = os.environ["NORRUVA_BASE_URL"]
api_key = os.environ["NORRUVA_API_KEY"]
id = "…"

resp = requests.get(
    f"{base_url}/api/v2/products/{id}/compliance/espr",
    headers={
        "Authorization": f"Bearer {api_key}"
    },
)
resp.raise_for_status()   # error body is the typed envelope: {"error": {"code", "message", "details"}}
data = resp.json()
package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	baseURL := os.Getenv("NORRUVA_BASE_URL")
	apiKey := os.Getenv("NORRUVA_API_KEY")
	id := "…"
	url := fmt.Sprintf("%s/api/v2/products/%s/compliance/espr", baseURL, id)

	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+apiKey+"")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Status) // non-2xx bodies use the typed envelope {error:{code,message,details}}
}
Errors you can branch on
Related
KEYcompliance:view

Evidence-pack export, POST, because the scope is a request body (productId required; optional regulationScope[], dateFrom, dateTo), not a query string. Scope compliance:view. KNOWN DEFECT: with an API key this currently returns 500 'Authentication required', the route passes its own scope gate and then delegates to a server action wrapped in withAuthentication(), which requires a browser user session an API key does not have. Not usable from a machine client until the route calls the use case directly. Beyond the core journey.

Request
POST {{baseUrl}}/api/v2/compliance/export/evidence-pack
Authorization: Bearer {{apiKey}}
Content-Type: application/json
curl -X POST "$BASE_URL/api/v2/compliance/export/evidence-pack" \
  -H "Authorization: Bearer $NORRUVA_API_KEY" \
  -H "Content-Type: application/json"
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;

const res = await fetch(`${baseUrl}/api/v2/compliance/export/evidence-pack`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": `application/json`
  }
});

if (!res.ok) {
  const err = await res.json();      // typed envelope: { error: { code, message, details? } }
  throw new Error(`${res.status} ${err.error?.code}: ${err.error?.message}`);
}
const data = await res.json();
import os, uuid, requests

base_url = os.environ["NORRUVA_BASE_URL"]
api_key = os.environ["NORRUVA_API_KEY"]

resp = requests.post(
    f"{base_url}/api/v2/compliance/export/evidence-pack",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": f"application/json"
    },
)
resp.raise_for_status()   # error body is the typed envelope: {"error": {"code", "message", "details"}}
data = resp.json()
package main

import (
	"fmt"
	"net/http"
	"os"
)

func main() {
	baseURL := os.Getenv("NORRUVA_BASE_URL")
	apiKey := os.Getenv("NORRUVA_API_KEY")
	url := fmt.Sprintf("%s/api/v2/compliance/export/evidence-pack", baseURL)

	req, _ := http.NewRequest("POST", url, nil)
	req.Header.Set("Authorization", "Bearer "+apiKey+"")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	fmt.Println(resp.Status) // non-2xx bodies use the typed envelope {error:{code,message,details}}
}
Related

Rule-pack catalog, typed diff, and amendment→task read models are governance surfaces that are Missing today.

Was this page helpful?
Thanks, noted.Feedback goes to the docs team by email.