Skip to main content
Version: 4.0.0

API Reference

The Pyx Identity Resolver exposes three API areas:

  • Identifier Management -- define the identifier schemes (namespaces and application identifiers) your resolver understands.
  • Link Registration -- register link responses against specific identifiers within a scheme.
  • Link Resolution -- resolve an identifier to its linked information. This is the public-facing surface that supply-chain actors hit.

For conceptual background on how the resolver works, see Understanding the Service.

Identifier Management and Link Registration require authentication. Link Resolution is fully public -- no credentials needed.

Swagger UI — interactive API explorer

Your running instance ships with Swagger UI at {your-domain}/api-docs. It lists every endpoint, every field, and lets you fire test requests right from the browser. The examples below are curated walkthroughs of the happy path; for the full schema detail, Swagger is the canonical source.

Placeholders used in this guide

Throughout the examples you will see these placeholder values. Replace them with values appropriate to your deployment.

PlaceholderMeaning
https://your-resolver.example.com/api/v4Your resolver's base URL (global prefix included)
YOUR_API_KEYA valid API key for authenticated endpoints
acmeAn example namespace
productAn example primary identifier shortcode -- used in Link Registration (identificationKeyType)
01The AI code for the same identifier -- used in Link Resolution URLs
12345An example primary identifier key
10/A1B2C3An example qualifier path segment (AI code 10 + value A1B2C3)

Authentication

Management endpoints (Identifier Management, Link Registration, and Link Management) require an API key passed as a Bearer token:

Authorization: Bearer YOUR_API_KEY

Link Resolution endpoints are public and require no authentication.


Identifier Management

Before you can register links, you need to tell the resolver which identifier schemes it should accept. An identifier scheme lives under a namespace and contains one or more application identifiers -- each with a type, regex pattern, and optional qualifiers.

Each application identifier has two names:

  • shortcode -- a human-readable name like product or batch. Used in Link Registration payloads (the identificationKeyType field).
  • ai (application identifier code) -- a short numeric code like 01 or 10. Used in Link Resolution URLs (the path segments that identify the item).

Both names refer to the same identifier -- shortcode is what you write when registering links, and ai is what appears in the public resolution URLs.

ai is optional for non-GS1 schemes

The ai field is required for GS1 schemes where a numeric AI code is meaningful (e.g. 01 for GTIN, 10 for batch/lot). For non-GS1 schemes, you may omit ai entirely -- the resolver will default it to the value of shortcode. When ai defaults to shortcode, resolution URLs use the shortcode in the path (e.g. /{namespace}/product/12345) rather than a numeric code.

Create an identifier scheme

Register a namespace called acme with two application identifiers: a primary identifier product (type I) and a qualifier batch (type Q).

curl -X POST https://your-resolver.example.com/api/v4/identifiers \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "acme",
"applicationIdentifiers": [
{
"title": "Product ID",
"label": "PRODUCT",
"shortcode": "product",
"ai": "01",
"type": "I",
"regex": "[A-Za-z0-9]+",
"qualifiers": ["10"]
},
{
"title": "Batch Number",
"label": "BATCH",
"shortcode": "batch",
"ai": "10",
"type": "Q",
"regex": "[A-Za-z0-9]+"
}
]
}'

A successful 200 response:

{
"message": "Application identifier upserted successfully"
}
Upsert behaviour

The endpoint uses upsert (create or update) semantics -- if the acme namespace already exists, the request replaces it. That is why it returns 200 rather than 201.

Retrieve an identifier scheme

Fetch the scheme you just created:

curl https://your-resolver.example.com/api/v4/identifiers?namespace=acme \
-H "Authorization: Bearer YOUR_API_KEY"

The response contains the full namespace object, including all application identifiers and their validation rules.

Omit the namespace query parameter to retrieve every registered scheme.

Delete an identifier scheme

Destructive — orphans all registered links

Deleting an identifier scheme does not cascade-delete the links registered under it, but it does make them unreachable. Resolution, registration, and management all require the scheme to exist, so any links that were registered against this namespace will be permanently orphaned once the scheme is removed.

Make sure you no longer need the scheme or any of its links before you call this endpoint.

curl -X DELETE https://your-resolver.example.com/api/v4/identifiers?namespace=acme \
-H "Authorization: Bearer YOUR_API_KEY"

For full schemas and validation rules, see the Swagger UI.


Once your identifier scheme is in place, you can register links against specific identifiers. Each registration targets a namespace + identifier key type + identifier key (and optionally a qualifier path), and contains one or more responses -- each pointing to a different URL with its own link type, language tags (BCP 47 entries in hreflang[] such as en, en-AU, or fr-CA), MIME type (the content format, such as text/html or application/pdf), and context (typically a geographic region code such as au or us). The full set of accepted per-variant fields is summarised under Variant fields below.

Register two responses for product 12345 in the acme namespace: a sustainability information page and a product datasheet.

curl -X POST https://your-resolver.example.com/api/v4/resolver \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "acme",
"identificationKeyType": "product",
"identificationKey": "12345",
"description": "Acme Widget",
"qualifierPath": "/",
"active": true,
"responses": [
{
"defaultLinkType": true,
"defaultMimeType": true,
"defaultContext": true,
"fwqs": false,
"active": true,
"linkType": "acme:sustainabilityInfo",
"hreflang": ["en"],
"context": "au",
"title": "Sustainability Information",
"targetUrl": "https://acme.example.com/products/12345/sustainability",
"mimeType": "text/html"
},
{
"defaultLinkType": false,
"defaultMimeType": true,
"defaultContext": true,
"fwqs": false,
"active": true,
"linkType": "acme:productDatasheet",
"hreflang": ["en"],
"context": "au",
"title": "Product Datasheet",
"targetUrl": "https://acme.example.com/products/12345/datasheet",
"mimeType": "application/pdf"
}
]
}'

A successful 201 response:

{
"message": "Link resolver registered successfully"
}
description field

The top-level description field replaces the legacy itemDescription field. Both are accepted for backwards compatibility, but description is preferred; itemDescription was deprecated in v3.0.0 and remains accepted as an alias.

Variant fields

Every entry in the responses[] array (a "variant") accepts the following fields. The full Swagger UI at /api is generated from the same DTOs and is the authoritative source for shape and validation.

FieldTypeRequiredPurpose
linkTypestringyesThe link relation type (prefix:key, e.g. acme:sustainabilityInfo). Validated against the vocabulary for the namespace's prefix.
titlestringyesHuman-readable label for the link.
targetUrlstring (URL)yesThe URL the link resolves to.
mimeTypestringyesAny RFC 6838 well-formed media type (e.g. text/html, application/pdf, application/vnd.acme.sbom+json). Custom and vendor-prefixed types are accepted; the value is not constrained to a registry.
contextstringyesContext qualifier (typically a regional code such as au, us, or eu). Part of the composite key.
hreflangstring[]noBCP 47 language tags advertised by this variant. A single variant can serve multiple tags. Matching is case-insensitive per RFC 4647 §2.1.
defaultLinkTypebooleanyesMarks this variant as the default for resolution requests that omit linkType. Only one variant per registration may set this to true.
defaultContextbooleanyesMarks this variant as the publisher's canonical fallback for its link type when no variant matches the requested language. Only one variant per link type may set this to true.
defaultMimeTypebooleanyesMarks this variant as the preferred MIME type within its (linkType, context) group. Only one variant per (linkType, context) may set this to true.
fwqsbooleanyesForward query string flag (see fwqs above).
activebooleanyesWhether the variant is served at resolution time. Set to false on soft delete; remains in the document but is omitted from the linkset.
accessRolestring[]noUNTP access roles that may retrieve this variant under variant-based disclosure. Values follow the URI pattern untp:accessRole#RoleName (Anonymous, Customer, Regulator, Recycler, Auditor, Owner); shorthand forms such as customer are expanded at resolution time.
encryptionMethodstringnoEncryption applied to the target content. One of none, AES-128, AES-256. Documents the encryption scheme; the resolver does not perform decryption itself (see fwqs + decryptionKey for key forwarding).
methodstringnoHTTP method the target URL expects (GET, POST, etc.). Advisory metadata for clients; the resolver itself only issues redirects, not requests.
publicbooleannoIndicates the URL itself is safe to publish in a public directory. Distinct from accessRole and encryptionMethod, which govern who may retrieve or decrypt the resource. A link may be public: true while still requiring an authorised role to fetch the content. An explicit public: false round-trips and is preserved separately from "not set".
relstring[]noAdditional link relation types qualifying the link beyond its primary linkType. The reserved value predecessor-version is silently stripped from publisher input; the server emits it itself on predecessor entries derived from version history.
linkIdstring (UUID)server-generatedUnique identifier assigned at registration time. Read-only; used to address the variant via the Link Management endpoints.

The composite key uniquely identifying a variant is (targetUrl, linkType, mimeType, context). hreflang, default flags, and the other fields are NOT part of the key. See Append-only behaviour below.

Link type validation

Every linkType value is validated against a namespace-specific vocabulary. Link types must use the format prefix:key (e.g. gs1:certificationInfo, untp:dpp).

  • The prefix must be a registered vocabulary. Currently supported: gs1, untp.
  • The key must be a known entry within that vocabulary.

Requests that use an unrecognised prefix or an unknown key are rejected with a 404 error. If you are registering links for a custom namespace (e.g. acme:), use the gs1 or untp prefix for any standard link types, or contact your resolver administrator to register a custom vocabulary.

Append-only behaviour

Link Registration is append-only at the response level. If a registration already exists for the same namespace, key type, key, and qualifier path, the new responses are appended to the existing ones.

Each response is identified by a composite key of targetUrl, linkType, mimeType, and context. Language (hreflang) is not part of the key; a single variant can advertise multiple BCP 47 tags via its hreflang[] array. If any incoming response matches an existing composite key (or a historical key from a previous version), the request is rejected with a 409 Conflict error -- duplicates are never silently merged or overwritten.

Forward query string (fwqs)

Each response carries a boolean fwqs (forward query string) flag. When fwqs is true and the resolution results in a redirect, any query parameters the caller includes on the resolution URL are appended to the target URL before the redirect is issued.

A common use case is passing a decryptionKey to a target that hosts encrypted content:

GET /api/v4/acme/01/12345?linkType=acme:certificationInfo&decryptionKey=a3f2b8c1

If the matched response has fwqs: true, the resolver redirects to:

https://example.com/cert?decryptionKey=a3f2b8c1

If the target URL already contains its own query string, the forwarded parameters are appended with &.

When fwqs is false (the default), query parameters are silently dropped during the redirect -- they are used only for resolution matching, not forwarded.

When to enable fwqs

Enable fwqs when the target URL needs caller-supplied parameters (e.g. decryption keys, access tokens, or tracking identifiers). Leave it disabled when the target URL is self-contained.

Default flags

Each response carries a set of default flags that control resolution fallback behaviour. For the full precedence explanation, see How It Works.

FlagScopePurpose
defaultLinkTypeEntire registrationUsed when no linkType is specified in a resolution request
defaultContextPer link typeThe publisher's canonical fallback for a link type. Returned when no variant matches the client's requested language.
defaultMimeTypePer link type + context (registration only)The scope prevents two variants in the same (link type, context) group from both claiming the default. The resolver does not consult context at resolution time; tier 2 returns the first language-matching variant whose flag is set.

Only one response can hold a given default flag within its scope. If you register a new response with a default flag set to true, the system automatically unsets that flag on any existing response in the same scope.

Qualifier paths

To register links for a specific batch of product 12345, set the qualifierPath to the qualifier AI code and value:

"qualifierPath": "/10/A1B2C3"

The qualifier AI codes (10 in this example) must correspond to valid qualifier types defined in your identifier scheme, and the values must match the regex pattern of that qualifier.

For full validation rules, see the Swagger UI.


Link Management provides CRUD operations for individual link responses that were created via Link Registration. Every response gets a unique linkId when it's registered -- Link Management endpoints use that linkId to retrieve, update, or delete individual responses.

All Link Management endpoints require authentication and live under /resolver/links.

Retrieve all responses for a given identifier:

curl "https://your-resolver.example.com/api/v4/resolver/links?namespace=acme&identificationKeyType=product&identificationKey=12345" \
-H "Authorization: Bearer YOUR_API_KEY"

You can narrow the results with optional filters:

ParameterDescription
qualifierPathFilter by qualifier path (e.g. /10/A1B2C3)
linkTypeFilter by link type
mimeTypeFilter by MIME type
hreflangFilter to responses whose hreflang[] contains the supplied BCP 47 tag

Retrieve a specific response by its linkId:

curl https://your-resolver.example.com/api/v4/resolver/links/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer YOUR_API_KEY"

Update any fields on a specific response. Only the fields you include in the body are changed -- everything else stays as it is:

curl -X PUT https://your-resolver.example.com/api/v4/resolver/links/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"targetUrl": "https://example.com/updated-cert",
"active": true
}'

All fields from the original registration are updatable, including targetUrl, linkType, mimeType, hreflang, context, active, fwqs, and the default flags.

Composite key conflict checks

If you update fields that form part of the composite key (targetUrl, linkType, mimeType, context), the service checks for conflicts against other current responses and historical keys from previous versions. The update is rejected with a 409 Conflict if the new composite key already exists.

By default, deletion is a soft delete -- the response is marked as inactive but remains in the document and its composite key stays protected:

curl -X DELETE https://your-resolver.example.com/api/v4/resolver/links/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer YOUR_API_KEY"

To permanently remove the response and free its composite key, pass ?hard=true:

curl -X DELETE "https://your-resolver.example.com/api/v4/resolver/links/a1b2c3d4-e5f6-7890-abcd-ef1234567890?hard=true" \
-H "Authorization: Bearer YOUR_API_KEY"
Hard delete is irreversible

A hard delete permanently removes the response and frees its composite key for reuse. The response cannot be recovered after a hard delete.

Every mutation -- whether it's a create, update, or delete -- is recorded in the identifier document's version history. Each history entry captures:

  • The document version number at the time of the change.
  • A timestamp.
  • A list of changes, each identifying the linkId, the action performed (created, updated, soft_deleted, or hard_deleted), and -- for updates that change composite key fields -- the previous values of targetUrl, linkType, mimeType, and context.

This history has two effects:

  1. Predecessor links in the linkset -- when a response's targetUrl changes, the previous URL appears as a predecessor-version entry in the RFC 9264 linkset. This means anyone resolving the identifier can discover where the linked resource used to live.

  2. Conflict protection -- when you register or update a response, the service checks the new composite key against both current responses and all historical composite keys. This prevents a new response from silently reusing a key that previously pointed somewhere else.

For example, if you update a response's targetUrl from https://example.com/v1/cert to https://example.com/v2/cert, the linkset for that identifier will include both:

  • The current link pointing to https://example.com/v2/cert
  • A predecessor-version entry pointing to https://example.com/v1/cert

Link Resolution is the public-facing API. No authentication is needed. You construct a URL from the namespace, the identifier's ai code, the identifier key, and (optionally) qualifier path segments -- then add query parameters to narrow down the response you want.

Notice the switch from registration to resolution: Link Registration uses the shortcode (product) in the identificationKeyType field, but resolution URLs conventionally use the ai code (01) in the path. The resolver accepts either -- if you use the shortcode in a resolution URL it will still resolve correctly -- but the ai code is the standard form for public-facing URLs.

For a detailed explanation of how the resolver picks the right response, see How It Works.

Request the sustainability information for product 12345:

curl -v https://your-resolver.example.com/api/v4/acme/01/12345?linkType=acme:sustainabilityInfo

The resolver responds with a 307 redirect to the target URL:

< HTTP/1.1 307 Temporary Redirect
< Location: https://acme.example.com/products/12345/sustainability
< Link: <https://your-resolver.example.com/api/v4/acme/01/12345?linkType=all>; rel="linkset"; type="application/linkset+json"

Note the Link header -- every resolution response includes a pointer to the full linkset for the identifier.

The path structure is /{namespace}/{identifierKeyType AI}/{identifierKey}. For qualified identifiers, append the qualifier segments:

curl -v https://your-resolver.example.com/api/v4/acme/01/12345/10/A1B2C3?linkType=acme:sustainabilityInfo

Additional query parameters

ParameterDescription
linkTypeThe link type to resolve (e.g. acme:sustainabilityInfo), or all for the full linkset
accessRoleAccess role for variant-based disclosure filtering. Accepts a full URI (e.g. myscheme:role#Admin) or a shorthand like customer or regulator (expanded to untp:accessRole#Customer by default)
decryptionKeyShared secret forwarded to the target URL when fwqs (forward query string) is enabled on the matched response

Content negotiation via Accept-Language

The resolver reads the standard HTTP Accept-Language header and matches each requested BCP 47 tag, in q-weight order, against the hreflang[] array on each registered variant. A variant matches when any of the client's tags appears in its hreflang[]. Matching is exact-string per RFC 4647; BCP 47 lookup fallback (e.g. en-GB matching a variant tagged en) is not yet implemented.

For example, to request a variant that advertises Australian English:

curl -v https://your-resolver.example.com/api/v4/acme/01/12345?linkType=acme:sustainabilityInfo \
-H "Accept-Language: en-AU"

A variant registered with hreflang: ["en-AU"] will match. When no variant matches any preferred tag, the resolver falls back through its precedence chain until it finds a suitable response.

Content negotiation via Accept (MIME type)

The resolver also reads the standard HTTP Accept header to match against the mimeType field on registered responses.

For example, to prefer a PDF version:

curl -v https://your-resolver.example.com/api/v4/acme/01/12345?linkType=acme:sustainabilityInfo \
-H "Accept: application/pdf"

If a response is registered with mimeType: "application/pdf", the resolver will prefer it. When no exact match is found, the resolver falls back to the response flagged as defaultMimeType, then continues through the precedence chain.

The Accept header also controls the response format:

Accept valueBehaviour
application/linkset+jsonReturns the full linkset as JSON instead of redirecting
application/linksetReturns the full set of Link header entries as application/linkset+json
Anything elseRedirects to the matched response's targetUrl (default)

Resolve with linkType=all

When you pass linkType=all, the resolver returns a linkset containing every active link for the identifier:

curl https://your-resolver.example.com/api/v4/acme/01/12345?linkType=all

A linkset is a structured JSON document (defined by RFC 9264) that lists every active link for an identifier in a machine-readable format.

Example response:

{
"linkset": [
{
"anchor": "https://your-resolver.example.com/api/v4/acme/01/12345",
"https://your-resolver.example.com/api/v4/voc/sustainabilityInfo": [
{
"href": "https://acme.example.com/products/12345/sustainability",
"title": "Sustainability Information",
"type": "text/html",
"hreflang": ["en"]
}
],
"https://your-resolver.example.com/api/v4/voc/productDatasheet": [
{
"href": "https://acme.example.com/products/12345/datasheet",
"title": "Product Datasheet",
"type": "application/pdf",
"hreflang": ["en"]
}
]
}
]
}
Link type URLs in the linkset

Notice that the link type keys in the linkset are full URLs (e.g., https://your-resolver.example.com/api/v4/voc/sustainabilityInfo), not the prefixed form you used during registration (acme:sustainabilityInfo). The resolver automatically expands the namespace prefix into the full vocabulary URL when building the linkset.

Resolver discovery

The .well-known/resolver endpoint returns metadata about the resolver itself -- which namespaces it supports, which link type vocabularies it uses, and its root URL:

curl https://your-resolver.example.com/api/v4/.well-known/resolver

Example response:

{
"name": "My Identity Resolver",
"resolverRoot": "https://your-resolver.example.com/api/v4",
"supportedLinkType": [
{
"namespace": "https://acme.example.com/voc/",
"prefix": "acme:",
"profile": "https://acme.example.com/voc/?show=linktypes"
}
],
"supportedPrimaryKeys": ["all"]
}

This endpoint is public and requires no authentication.

For full query parameters and response schemas, see the Swagger UI.