Resolver & public
How the outside world reaches a published passport. See Digital Link & resolution for audience tiers and the two documented deviations.
| GET | /api/v2/01/{gtin}/21/{serial}Resolve a Digital Link |
| GET | /api/v2/public/gs1/{…segments}Machine resolver (GS1 DL) |
| GET | /api/v2/.well-known/gs1resolverResolver discovery document |
| GET | /api/v2/public/passport/{uid}Public passport JSON |
| GET | /api/v2/public/passport/{uid}/verifyVerify a public passport |
| GET | /api/v2/public/openapi.jsonPublic OpenAPI spec |
GS1 01/21 resolution → 302 to /p/{uid} (the public uid = the product id), measured 0.16s. Content-negotiated linkset via Accept: application/linkset+json (RFC 9264).
GET {{baseUrl}}/api/v2/01/{gtin}/21/{serial}
Authorization: Bearer edge-gate-placeholdercurl -X GET "$BASE_URL/api/v2/01/{gtin}/21/{serial}" \
-H "Authorization: Bearer edge-gate-placeholder"const baseUrl = process.env.NORRUVA_BASE_URL;
const gtin = "…";
const serial = "…";
const res = await fetch(`${baseUrl}/api/v2/01/${gtin}/21/${serial}`, {
method: "GET",
headers: {
"Authorization": `Bearer edge-gate-placeholder`
}
});
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"]
gtin = "…"
serial = "…"
resp = requests.get(
f"{base_url}/api/v2/01/{gtin}/21/{serial}",
headers={
"Authorization": f"Bearer edge-gate-placeholder"
},
)
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")
gtin := "…"
serial := "…"
url := fmt.Sprintf("%s/api/v2/01/%s/21/%s", baseURL, gtin, serial)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer edge-gate-placeholder")
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}}
}Catch-all GS1 Digital Link segments: browsers → 302 /p/{uid}; machine Accept → JSON linkset.
GET {{baseUrl}}/api/v2/public/gs1/{…segments}
curl -X GET "$BASE_URL/api/v2/public/gs1/{…segments}"const baseUrl = process.env.NORRUVA_BASE_URL;
const segments = "…";
const res = await fetch(`${baseUrl}/api/v2/public/gs1/${segments}`, {
method: "GET",
headers: {
}
});
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"]
segments = "…"
resp = requests.get(
f"{base_url}/api/v2/public/gs1/{segments}",
headers={
},
)
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")
segments := "…"
url := fmt.Sprintf("%s/api/v2/public/gs1/%s", baseURL, segments)
req, _ := http.NewRequest("GET", url, nil)
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}}
}GS1 resolver discovery: supported keys, qualifiers, link types.
GET {{baseUrl}}/api/v2/.well-known/gs1resolver
curl -X GET "$BASE_URL/api/v2/.well-known/gs1resolver"const baseUrl = process.env.NORRUVA_BASE_URL;
const res = await fetch(`${baseUrl}/api/v2/.well-known/gs1resolver`, {
method: "GET",
headers: {
}
});
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"]
resp = requests.get(
f"{base_url}/api/v2/.well-known/gs1resolver",
headers={
},
)
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")
url := fmt.Sprintf("%s/api/v2/.well-known/gs1resolver", baseURL)
req, _ := http.NewRequest("GET", url, nil)
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}}
}Audience-filtered passport JSON. Anonymous callers get the consumer minimum; only a cryptographically verified X-DPP-Access-Token elevates disclosure. Every public read is logged with its audience.
GET {{baseUrl}}/api/v2/public/passport/{uid}
curl -X GET "$BASE_URL/api/v2/public/passport/{uid}"const baseUrl = process.env.NORRUVA_BASE_URL;
const uid = "…";
const res = await fetch(`${baseUrl}/api/v2/public/passport/${uid}`, {
method: "GET",
headers: {
}
});
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"]
uid = "…"
resp = requests.get(
f"{base_url}/api/v2/public/passport/{uid}",
headers={
},
)
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")
uid := "…"
url := fmt.Sprintf("%s/api/v2/public/passport/%s", baseURL, uid)
req, _ := http.NewRequest("GET", url, nil)
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}}
}Signature + anchor verification for a published passport.
GET {{baseUrl}}/api/v2/public/passport/{uid}/verify
curl -X GET "$BASE_URL/api/v2/public/passport/{uid}/verify"const baseUrl = process.env.NORRUVA_BASE_URL;
const uid = "…";
const res = await fetch(`${baseUrl}/api/v2/public/passport/${uid}/verify`, {
method: "GET",
headers: {
}
});
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"]
uid = "…"
resp = requests.get(
f"{base_url}/api/v2/public/passport/{uid}/verify",
headers={
},
)
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")
uid := "…"
url := fmt.Sprintf("%s/api/v2/public/passport/%s/verify", baseURL, uid)
req, _ := http.NewRequest("GET", url, nil)
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}}
}Public machine-readable spec — pull it for discovery in your smoke tests.
GET {{baseUrl}}/api/v2/public/openapi.json
curl -X GET "$BASE_URL/api/v2/public/openapi.json"const baseUrl = process.env.NORRUVA_BASE_URL;
const res = await fetch(`${baseUrl}/api/v2/public/openapi.json`, {
method: "GET",
headers: {
}
});
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"]
resp = requests.get(
f"{base_url}/api/v2/public/openapi.json",
headers={
},
)
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")
url := fmt.Sprintf("%s/api/v2/public/openapi.json", baseURL)
req, _ := http.NewRequest("GET", url, nil)
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}}
}401 to anonymous scanners sending no Authorization header; send Authorization: Bearer edge-gate-placeholder until fixed. Draft passports return 404 "not found or not published".