Developer Docs
Sandbox · API v2PlaygroundGet API keys

Import / bulk

Ingest many products at once with row-isolated errors and a correction loop. Concept and lifecycle: Import jobs.

POST/api/v2/import/jobsCreate an import job
GET/api/v2/import/jobs/{jobId}Import job status
GET/api/v2/import/jobs/{jobId}/errorsRow-level errors
GET/api/v2/import/jobs/{jobId}/correction-templateCorrection template
POST/api/v2/import/jobs/{jobId}/confirmConfirm an import job
POST/api/v2/ingest/productsMachine batch on-ramp
GET/api/v2/ingest/{jobId}Machine on-ramp job status
POST/api/v2/import/aasxAASX package import
POST/api/v2/importSingle-shot CSV import
GET/api/v2/import/templates/{regulationCode}Per-regulation template
KEY

Multipart CSV (UTF-8, ≤ 10 MB, up to 10,000 rows, magic-byte validated) → 202 { jobId }. Row failures are isolated, one bad row does not fail the batch.

Parameters
filerequiredbody · filemultipart/form-data field. UTF-8 CSV, ≤ 10 MB, ≤ 10,000 rows, magic-byte validated.
regulationCodeoptionalbody · stringmultipart field. The FULL schema-registry code (EU-ESPR-2024-1781-ELEC, EU-BAT-2023-1542, EU-ESPR-2024-1781-TEX). Optional, but GET …/correction-template REQUIRES it: without it the job stores an empty regulation code and the correction template is a permanent 400 'no associated regulation code' no matter how many rows failed. Send it on every upload you might need to correct.
Request
POST {{baseUrl}}/api/v2/import/jobs
Authorization: Bearer {{apiKey}}
Content-Type: application/json
curl -X POST "$BASE_URL/api/v2/import/jobs" \
  -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/import/jobs`, {
  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/import/jobs",
    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/import/jobs", 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}}
}
Response

202 Accepted → { jobId }. Re-submitting the same file is a no-op. A tenant may hold at most 3 ACTIVE (unconfirmed) jobs, the 4th is 400 "You have 3 active import jobs". Always call …/confirm; an unconfirmed job stays active, never expires, and wedges the next upload.

Emits events
import.completedimport.failed
Related
KEY

Status / preview for a job.

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

const res = await fetch(`${baseUrl}/api/v2/import/jobs/${jobId}`, {
  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"]
job_id = "…"

resp = requests.get(
    f"{base_url}/api/v2/import/jobs/{job_id}",
    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")
	jobId := "…"
	url := fmt.Sprintf("%s/api/v2/import/jobs/%s", baseURL, jobId)

	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

Row-level failures, isolated and queryable.

Request
GET {{baseUrl}}/api/v2/import/jobs/{jobId}/errors
Authorization: Bearer {{apiKey}}
curl -X GET "$BASE_URL/api/v2/import/jobs/{jobId}/errors" \
  -H "Authorization: Bearer $NORRUVA_API_KEY"
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;
const jobId = "…";

const res = await fetch(`${baseUrl}/api/v2/import/jobs/${jobId}/errors`, {
  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"]
job_id = "…"

resp = requests.get(
    f"{base_url}/api/v2/import/jobs/{job_id}/errors",
    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")
	jobId := "…"
	url := fmt.Sprintf("%s/api/v2/import/jobs/%s/errors", baseURL, jobId)

	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

Fixable re-upload template generated from the job's failures. Two preconditions, both 400 when unmet: the job must HAVE failures (call it only when GET …/errors is non-empty, and row failures only exist after …/confirm has run), and the job must carry a regulationCode, which is set only if you passed that multipart field at upload time.

Request
GET {{baseUrl}}/api/v2/import/jobs/{jobId}/correction-template
Authorization: Bearer {{apiKey}}
curl -X GET "$BASE_URL/api/v2/import/jobs/{jobId}/correction-template" \
  -H "Authorization: Bearer $NORRUVA_API_KEY"
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;
const jobId = "…";

const res = await fetch(`${baseUrl}/api/v2/import/jobs/${jobId}/correction-template`, {
  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"]
job_id = "…"

resp = requests.get(
    f"{base_url}/api/v2/import/jobs/{job_id}/correction-template",
    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")
	jobId := "…"
	url := fmt.Sprintf("%s/api/v2/import/jobs/%s/correction-template", baseURL, jobId)

	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
KEY

Apply the job, drives it terminal. Always call this.

Request
POST {{baseUrl}}/api/v2/import/jobs/{jobId}/confirm
Authorization: Bearer {{apiKey}}
Content-Type: application/json
curl -X POST "$BASE_URL/api/v2/import/jobs/{jobId}/confirm" \
  -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 jobId = "…";

const res = await fetch(`${baseUrl}/api/v2/import/jobs/${jobId}/confirm`, {
  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"]
job_id = "…"

resp = requests.post(
    f"{base_url}/api/v2/import/jobs/{job_id}/confirm",
    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")
	jobId := "…"
	url := fmt.Sprintf("%s/api/v2/import/jobs/%s/confirm", baseURL, jobId)

	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
KEYingest:write

The JSON batch path ERP-style integrations use, no multipart. Strict ERP-flavoured schema (unknown keys rejected); referenceId is the per-row idempotency key, so re-submitting the same rows never duplicates products.

Parameters
sourceSystemrequiredbody · stringe.g. "tsc-middleware".
products[].referenceIdrequiredbody · stringPer-row identity, idempotent re-submit.
products[].namerequiredbody · stringProduct name.
callbackUrloptionalbody · stringOptional completion callback.
Request
POST {{baseUrl}}/api/v2/ingest/products
Authorization: Bearer {{apiKey}}
Content-Type: application/json

{
  "sourceSystem": "tsc-middleware",
  "products": [{
    "referenceId": "erp-4711",
    "name": "EcoCell Battery Pack",
    "description": "…", "gtin": "…",
    "category": "Electronics",
    "materials": [{ "name": "lithium", "percentage": 5.2 }],
    "manufacturing": { "facility": "Gigafactory 1", "country": "DE" },
    "customData": { "line": "A" }
  }],
  "callbackUrl": "https://integrator.example/hooks/ingest"
}
curl -X POST "$BASE_URL/api/v2/ingest/products" \
  -H "Authorization: Bearer $NORRUVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "sourceSystem": "tsc-middleware",
  "products": [
    {
      "referenceId": "erp-4711",
      "name": "EcoCell Battery Pack",
      "description": "…",
      "gtin": "…",
      "category": "Electronics",
      "materials": [
        {
          "name": "lithium",
          "percentage": 5.2
        }
      ],
      "manufacturing": {
        "facility": "Gigafactory 1",
        "country": "DE"
      },
      "customData": {
        "line": "A"
      }
    }
  ],
  "callbackUrl": "https://integrator.example/hooks/ingest"
}'
const baseUrl = process.env.NORRUVA_BASE_URL;
const apiKey = process.env.NORRUVA_API_KEY;

const res = await fetch(`${baseUrl}/api/v2/ingest/products`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${apiKey}`,
    "Content-Type": `application/json`
  },
  body: JSON.stringify({
    "sourceSystem": "tsc-middleware",
    "products": [
      {
        "referenceId": "erp-4711",
        "name": "EcoCell Battery Pack",
        "description": "…",
        "gtin": "…",
        "category": "Electronics",
        "materials": [
          {
            "name": "lithium",
            "percentage": 5.2
          }
        ],
        "manufacturing": {
          "facility": "Gigafactory 1",
          "country": "DE"
        },
        "customData": {
          "line": "A"
        }
      }
    ],
    "callbackUrl": "https://integrator.example/hooks/ingest"
  })
});

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/ingest/products",
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": f"application/json"
    },
    json={
      "sourceSystem": "tsc-middleware",
      "products": [
        {
          "referenceId": "erp-4711",
          "name": "EcoCell Battery Pack",
          "description": "…",
          "gtin": "…",
          "category": "Electronics",
          "materials": [
            {
              "name": "lithium",
              "percentage": 5.2
            }
          ],
          "manufacturing": {
            "facility": "Gigafactory 1",
            "country": "DE"
          },
          "customData": {
            "line": "A"
          }
        }
      ],
      "callbackUrl": "https://integrator.example/hooks/ingest"
    },
)
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/ingest/products", baseURL)

	payload := []byte(`{
  "sourceSystem": "tsc-middleware",
  "products": [
    {
      "referenceId": "erp-4711",
      "name": "EcoCell Battery Pack",
      "description": "…",
      "gtin": "…",
      "category": "Electronics",
      "materials": [
        {
          "name": "lithium",
          "percentage": 5.2
        }
      ],
      "manufacturing": {
        "facility": "Gigafactory 1",
        "country": "DE"
      },
      "customData": {
        "line": "A"
      }
    }
  ],
  "callbackUrl": "https://integrator.example/hooks/ingest"
}`)
	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}}
}
Response

202 Accepted → { jobId }. Measured 2026-07-22: 10,000 rows terminal in 67.5 s, 25/25 induced failures isolated row-level, idempotent re-submit with zero duplicates.

Errors you can branch on
Emits events
import.completedimport.failed
Related
KEY

Job status / row outcomes for the machine on-ramp.

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

const res = await fetch(`${baseUrl}/api/v2/ingest/${jobId}`, {
  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"]
job_id = "…"

resp = requests.get(
    f"{base_url}/api/v2/ingest/{job_id}",
    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")
	jobId := "…"
	url := fmt.Sprintf("%s/api/v2/ingest/%s", baseURL, jobId)

	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

Multipart .aasx (OPC/ZIP) package → Draft product via the canonical create contract. XML-only payloads are refused honestly.

Request
POST {{baseUrl}}/api/v2/import/aasx
Authorization: Bearer {{apiKey}}
Content-Type: application/json
curl -X POST "$BASE_URL/api/v2/import/aasx" \
  -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/import/aasx`, {
  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/import/aasx",
    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/import/aasx", 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
KEY

Single-shot CSV import (≤ 50 MB).

Request
POST {{baseUrl}}/api/v2/import
Authorization: Bearer {{apiKey}}
Content-Type: application/json
curl -X POST "$BASE_URL/api/v2/import" \
  -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/import`, {
  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/import",
    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/import", 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
KEY

Per-regulation column template (same generator as category templates, keyed by regulation code). regulationCode is the FULL schema_registry code, not a short name, e.g. EU-BAT-2023-1542, EU-ESPR-2024-1781-ELEC, EU-ESPR-2024-1781-TEX. Guessing 'ESPR' returns 404. Send a key: the handler itself is declared public, but this path is NOT on the edge allowlist, so the edge middleware returns 401 to an anonymous caller before the handler runs, the same edge-gate deviation as the GS1 resolver. Any credential-shaped Bearer header passes the edge.

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

const res = await fetch(`${baseUrl}/api/v2/import/templates/${regulationCode}`, {
  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"]
regulation_code = "…"

resp = requests.get(
    f"{base_url}/api/v2/import/templates/{regulation_code}",
    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")
	regulationCode := "…"
	url := fmt.Sprintf("%s/api/v2/import/templates/%s", baseURL, regulationCode)

	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
Was this page helpful?
Thanks, noted.Feedback goes to the docs team by email.