Skip to main content
    Skip to content
    NorruvaDeveloper Docs
    Sandbox · verified 2026-07-29
    This page
    Whole docs
    Machine formats

    PlaygroundGet API keys
    IntroductionQuickstartAuthenticationErrors & conventionsSandbox & environments
    Entity modelProducts & categoriesPassports & versionsDigital Link & resolutionCompliance & regulationsWebhooks & eventsAutoID print loopImport jobsObservability & audit
    OverviewAuth & API keysProductsCompliance & regulationsPassportsResolver & publicWebhooksPrint jobs & devicesImport / bulkObservabilityBeyond happy path
    Integration playbookWebhook receiver guideRun a print deviceDeviations & gotchas
    EN 18222 API methodsAnnex ZA — ESPR correspondence
    Docs/API reference/Compliance & regulations

    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
    Compliance & regulationsQuickstart
    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 (ATLAS-F017), 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
    Compliance & regulationsQuickstart
    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
    Compliance & regulations
    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
    410
    Related
    Compliance & regulations
    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
    Compliance & regulations

    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.
    ← PreviousProductsNext →Passports
    On this page
    POST /compliance/regulations/selectPOST /compliance/validateGET /products/{id}/compliance/espr/fullGET /products/{id}/compliance/esprPOST /compliance/export/evidence-pack
    Norruva DPP API · sandbox developer docsGenerated 2026-07-29 · PRD-aligned (TSC roadmap rev 2) · statuses reflect E2E-verified sandbox behaviour — not marketing