{
  "openapi": "3.0.0",
  "info": {
    "title": "UNTP Identity Resolver API",
    "description": "Implementation guidance for the UNTP Identity Resolver (IDR) specification. This API implements the Discover-Resolve-Verify workflow defined by the UNTP IDR specification, enabling supply chain actors to resolve product and facility identifiers to linked digital credentials such as Digital Product Passports (DPPs), Digital Conformity Credentials (DCCs), Digital Facility Records (DFRs), and Digital Traceability Events (DTEs).\n\n## Standards Compliance\n\n- **ISO/IEC 18975** — Structured-path URI syntax for identifier resolution\n- **IETF RFC 9264** — Linkset media type for typed link collections\n- **W3C DID Resolution** — Decentralised identifier resolution patterns\n- **IETF RFC 8615** — Well-known URI discovery mechanism\n\n## Functional Layers\n\nThe API is organized into three functional layers:\n\n1. **Resolution (public)** — Anonymous, unauthenticated resolution of identifiers to linksets or redirects. This is the core conformity surface that supply chain verifiers interact with.\n2. **Link Registration (management)** — Authenticated API for registering and managing link resolver entries that associate identifiers with typed link targets.\n3. **Identifier Scheme Management** — Authenticated API for configuring permitted identifier schemes (namespaces and application identifiers) before link registration can occur.\n\n## Normative Requirements\n\nThis API addresses UNTP IDR normative requirements IDR-01 through IDR-14. Key requirements include: IDR-01 (identifier scheme registration), IDR-02 (link registration), IDR-06/07/08 (resolution with link type, language, and context negotiation), IDR-09 (qualifier-based logical grouping), IDR-10 (secure targets with encryption and access roles), and IDR-14 (resolver description discovery).",
    "version": "0.7.0"
  },
  "tags": [
    {
      "name": "Link Resolution",
      "description": "Public, anonymous resolution of identifiers to linksets or redirect targets. Implements IDR-06 (link type filtering), IDR-07 (linkset response), and IDR-08 (language and context negotiation). Resolution paths conform to the ISO/IEC 18975 structured-path URI syntax: /{namespace}/{identifierKeyType}/{identifierKey}/{qualifiers}."
    },
    {
      "name": "Link Registration",
      "description": "Authenticated API for registering link resolver entries that associate an identifier with one or more typed link targets (the responses array). Each entry represents one identified item with multiple link variants differentiated by link type, language, MIME type, context, and access role. Implements IDR-02 (one carrier, many links), IDR-09 (qualifier path grouping), and IDR-10 (secure targets)."
    },
    {
      "name": "Link Management",
      "description": "CRUD operations on individual links within a registered resolver entry. Use these endpoints to update target URLs, rotate credentials, change access roles, toggle active status, or soft/hard delete individual link variants without affecting other links on the same identifier."
    },
    {
      "name": "Identifiers",
      "description": "Manage permitted identifier schemes (namespaces and their application identifiers). An identifier scheme must be registered before any link registration can reference it. Each scheme defines a namespace, a set of application identifiers (primary keys, qualifiers, data attributes) with regex validation patterns, and their relationships. Implements IDR-01 (scheme registration), IDR-03 (identifier validation), and IDR-04 (scheme configuration)."
    }
  ],
  "servers": [
    {
      "url": "https://{resolverDomain}/api/{version}",
      "description": "UNTP Identity Resolver API. Replace the variables with your resolver's domain and API version.",
      "variables": {
        "resolverDomain": {
          "default": "resolver.example.com",
          "description": "The domain name of the identity resolver service. Each registry operator deploys their own resolver instance."
        },
        "version": {
          "default": "0.7.0",
          "description": "API version. Follows the UNTP specification version that the resolver implements."
        }
      }
    }
  ],
  "paths": {
    "/health-check": {
      "get": {
        "operationId": "AppController_healthCheck",
        "summary": "Health check",
        "parameters": [],
        "responses": {
          "201": {
            "description": "Health check status",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "status": "OK"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/identifiers": {
      "post": {
        "operationId": "IdentifierManagementController_upsertIdentifier",
        "summary": "Create or update identifier",
        "description": "Registers or updates an identifier scheme configuration. Defines the namespace, application identifiers (primary keys, qualifiers, data attributes), their regex validation patterns, and relationships. An identifier scheme MUST be registered before any link registration can occur for that namespace. If the namespace already exists, the configuration is replaced (upsert semantics).",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IdentifierDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Identifier scheme created or updated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Application identifier upserted successfully"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error. The request body does not conform to the IdentifierDto schema — for example, missing required fields, invalid regex patterns, or duplicate shortcodes within the same namespace.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "errors": [
                      {
                        "field": "applicationIdentifiers.0.shortcode",
                        "message": "shortcode must be a string"
                      },
                      {
                        "field": "applicationIdentifiers.0.regex",
                        "message": "regex must be a string"
                      }
                    ]
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Identifiers"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      },
      "delete": {
        "operationId": "IdentifierManagementController_deleteIdentifier",
        "summary": "Delete identifier by namespace",
        "description": "Permanently removes an identifier scheme and all its application identifier configurations. Existing link resolver entries that reference this namespace will no longer resolve.",
        "parameters": [
          {
            "name": "namespace",
            "required": true,
            "in": "query",
            "description": "The namespace of the identifier scheme to delete (e.g. \"gs1\", \"nlis\"). Must match an existing registered scheme.",
            "schema": {
              "type": "string",
              "example": "example-identifier-scheme"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identifier scheme deleted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Application identifier deleted successfully"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request - Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "error": "Namespace is required"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Identifiers"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      },
      "get": {
        "operationId": "IdentifierManagementController_getIdentifier",
        "summary": "Get identifier by namespace",
        "description": "Retrieves the configuration for one or all registered identifier schemes. When a namespace is specified, returns that scheme's configuration. When omitted, returns all registered schemes. Useful for inspecting which identifier types and validation patterns are currently configured.",
        "parameters": [
          {
            "name": "namespace",
            "required": false,
            "in": "query",
            "description": "The namespace of the identifier scheme to retrieve (e.g. \"gs1\", \"nlis\"). If not provided, all registered identifier schemes are returned.",
            "schema": {
              "type": "string",
              "example": "example-identifier-scheme"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identifier scheme(s) retrieved successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/IdentifierDto"
                    },
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/IdentifierDto"
                      }
                    }
                  ]
                },
                "examples": {
                  "oneNamespace": {
                    "summary": "Single Namespace Response",
                    "value": {
                      "namespace": "example-identifier-scheme",
                      "applicationIdentifiers": [
                        {
                          "title": "Global Trade Item Number (GTIN)",
                          "label": "GTIN",
                          "shortcode": "gtin",
                          "ai": "01",
                          "type": "I",
                          "qualifiers": [
                            "10"
                          ],
                          "regex": "(\\d{12,14}|\\d{8})"
                        },
                        {
                          "title": "Batch or Lot Number",
                          "label": "BATCH/LOT",
                          "shortcode": "lot",
                          "ai": "10",
                          "type": "Q",
                          "regex": "([\\x21-\\x22\\x25-\\x2F\\x30-\\x39\\x41-\\x5A\\x5F\\x61-\\x7A]{0,20})"
                        }
                      ]
                    }
                  },
                  "allNamespaces": {
                    "summary": "All Namespaces Response",
                    "value": [
                      {
                        "namespace": "example-identifier-scheme",
                        "applicationIdentifiers": [
                          {
                            "title": "Global Trade Item Number (GTIN)",
                            "label": "GTIN",
                            "shortcode": "gtin",
                            "ai": "01",
                            "type": "I",
                            "regex": "(\\d{12,14}|\\d{8})"
                          }
                        ]
                      },
                      {
                        "namespace": "integrity-systems",
                        "applicationIdentifiers": [
                          {
                            "title": "NLIS ID",
                            "label": "NLISID",
                            "shortcode": "nlisid",
                            "ai": "01",
                            "format": "N14",
                            "type": "I",
                            "regex": "(\\d{12,14}|\\d{8})"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "tags": [
          "Identifiers"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/resolver": {
      "post": {
        "operationId": "LinkRegistrationController_create",
        "summary": "Register a new link resolver",
        "description": "Registers a link resolver entry associating an identifier with one or more typed link targets. Each entry in the responses array represents a link variant — a combination of link type, language, MIME type, geographic context, and access role — per UNTP IDR-06. This implements the core IDR-02 requirement: one identified item (carrier) linked to many digital credentials and documents.\n\nUpsert semantics: if a resolver entry already exists for the same namespace + identificationKeyType + identificationKey + qualifierPath combination, new responses are added and existing responses with matching linkType + mimeType + context are updated. hreflang is metadata on the variant (not part of the matching key) — a single variant can serve multiple languages.",
        "parameters": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateLinkRegistrationDto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Link resolver entry registered successfully. The identifier is now resolvable via the resolution endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Link resolver registered successfully"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error. Common causes: the namespace or identificationKeyType is not registered, the identificationKey does not match the registered regex pattern, or required response fields are missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity. The request is syntactically valid but semantically incorrect — for example, a qualifier path that references an AI not registered as a qualifier for the primary identifier type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          }
        },
        "tags": [
          "Link Registration"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/resolver/links": {
      "get": {
        "operationId": "LinkManagementController_listLinks",
        "summary": "List all active links for an identifier",
        "description": "Retrieves all active links registered for a specific identifier, optionally filtered by linkType, mimeType, or language. Returns the full link variant details including target URLs, access roles, and encryption metadata. This is a management endpoint for inspecting registered links — for public resolution, use the /{namespace}/{identifierKeyType}/{identifierKey} path instead.",
        "parameters": [
          {
            "name": "namespace",
            "required": true,
            "in": "query",
            "description": "The namespace of the identifier scheme (e.g. \"gs1\", \"nlis\"). Must match a registered identifier scheme.",
            "example": "example-identifier-scheme",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "identificationKeyType",
            "required": true,
            "in": "query",
            "description": "The application identifier shortcode (e.g. \"gtin\", \"nlisid\"). Must be a registered shortcode within the specified namespace.",
            "example": "gtin",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "identificationKey",
            "required": true,
            "in": "query",
            "description": "The actual identifier value (e.g. GTIN digits \"09359502000010\"). Must match the regex pattern registered for this identifier type.",
            "example": "12345678901234",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "qualifierPath",
            "required": false,
            "in": "query",
            "description": "Optional qualifier path segment for sub-identification (e.g. \"/10/LOT123\" for batch). Use \"/\" for the base identifier without qualifiers. Implements IDR-09 logical grouping.",
            "example": "/10/12345678901234567890",
            "schema": {
              "default": "/",
              "type": "string"
            }
          },
          {
            "name": "linkType",
            "required": false,
            "in": "query",
            "description": "Filter results by link type. Use UNTP link relations (e.g. \"untp:dpp\", \"untp:dcc\") or scheme-specific types (e.g. \"gs1:certificationInfo\").",
            "example": "example-identifier-scheme:certificationInfo",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "mimeType",
            "required": false,
            "in": "query",
            "description": "Filter results by MIME type of the target resource (e.g. \"application/json\", \"application/vc+ld+json\", \"application/pdf\").",
            "example": "application/json",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hreflang",
            "required": false,
            "in": "query",
            "description": "Filter results by BCP 47 language tag (e.g. \"en\", \"de\", \"ja\"). Returns only link variants whose hreflang array contains the specified tag.",
            "example": "en",
            "schema": {
              "type": "string",
              "pattern": "(^\\w{2}$)|(^\\w{2}-\\w{2}$)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Links retrieved successfully. Returns an array of link variants matching the query filters."
          },
          "400": {
            "description": "Validation error. The namespace or identificationKeyType is not registered, or required query parameters are missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          },
          "404": {
            "description": "No link resolver entry found for this identifier. The namespace + identificationKeyType + identificationKey + qualifierPath combination has no registered links."
          },
          "500": {
            "description": "Internal Server Error"
          }
        },
        "tags": [
          "Link Management"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/resolver/links/{linkId}": {
      "get": {
        "operationId": "LinkManagementController_getLink",
        "summary": "Get a specific link by linkId",
        "description": "Retrieves the full details of a single link variant by its server-generated identifier. Returns all properties including target URL, link type, access roles, encryption metadata, and active status.",
        "parameters": [
          {
            "name": "linkId",
            "required": true,
            "in": "path",
            "description": "The server-generated unique identifier for the link variant (UUID format).",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Link variant retrieved successfully."
          },
          "404": {
            "description": "No link found with the specified linkId."
          },
          "500": {
            "description": "Internal Server Error"
          }
        },
        "tags": [
          "Link Management"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      },
      "put": {
        "operationId": "LinkManagementController_updateLink",
        "summary": "Update a specific link",
        "description": "Updates properties of a single link variant. All fields are optional (partial update semantics) — only the fields included in the request body are modified. Useful for rotating target URLs, changing access roles, toggling active status, or updating encryption methods without recreating the entire resolver entry.",
        "parameters": [
          {
            "name": "linkId",
            "required": true,
            "in": "path",
            "description": "The server-generated unique identifier for the link variant to update (UUID format).",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateLinkDto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link variant updated successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Link updated successfully"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error. Invalid field values — for example, an unrecognised MIME type or malformed target URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          },
          "404": {
            "description": "No link found with the specified linkId."
          },
          "500": {
            "description": "Internal Server Error"
          }
        },
        "tags": [
          "Link Management"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      },
      "delete": {
        "operationId": "LinkManagementController_deleteLink",
        "summary": "Delete a specific link (soft by default, hard with ?hard=true)",
        "description": "Removes a link variant. By default, performs a soft delete: the link is deactivated (active=false) and excluded from resolution responses, but retained in storage for audit trail purposes. When hard=true, the link is permanently removed from the database. Soft-deleted links can be reactivated by updating their active status via PUT.",
        "parameters": [
          {
            "name": "linkId",
            "required": true,
            "in": "path",
            "description": "The server-generated unique identifier for the link variant to delete (UUID format).",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hard",
            "required": false,
            "in": "query",
            "description": "Set to true for permanent deletion. When false or omitted, the link is soft-deleted (deactivated but retained for audit).",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Link variant deleted successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "message": "Link deleted successfully"
                  }
                }
              }
            }
          },
          "404": {
            "description": "No link found with the specified linkId."
          },
          "500": {
            "description": "Internal Server Error"
          }
        },
        "tags": [
          "Link Management"
        ],
        "security": [
          {
            "bearer": []
          }
        ]
      }
    },
    "/{namespace}/{identifierKeyType}/{identifierKey}/{secondaryIdentifiersPath}": {
      "get": {
        "operationId": "LinkResolutionController_resolveClone",
        "summary": "Resolve a link resolver for an identifier",
        "description": "Core UNTP identity resolution endpoint. Resolves an identifier to its registered link targets using the ISO/IEC 18975 structured-path URI syntax.\n\nResolution behaviour depends on the linkType parameter:\n- When linkType is omitted or set to a specific type: returns a **302 redirect** to the default link target URL matching the requested type, language, and context.\n- When linkType is \"all\": returns a **full RFC 9264 linkset** as JSON containing all registered link variants for the identifier.\n\nThe path structure follows ISO 18975: `/{namespace}/{identifierKeyType}/{identifierKey}/{qualifiers}` where the namespace identifies the scheme, the identifierKeyType identifies the class of identifier, and optional qualifiers provide sub-identification (e.g. batch/lot, serial number).\n\nThis endpoint is public and does not require authentication.",
        "parameters": [
          {
            "name": "namespace",
            "required": true,
            "in": "path",
            "description": "Identifies the identifier scheme (e.g. \"gs1\" for GS1 identifiers, \"nlis\" for Australian livestock). Maps to the scheme's registered configuration. Must match a namespace previously registered via the /identifiers endpoint.",
            "example": "example-identifier-scheme",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "identifierKeyType",
            "required": true,
            "in": "path",
            "description": "The application identifier shortcode or AI code that determines the primary identifier class (e.g. \"01\" for GTIN, \"gln\" for GLN, \"giai\" for Global Individual Asset Identifier). Must be a registered shortcode or AI within the specified namespace.",
            "example": "01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "identifierKey",
            "required": true,
            "in": "path",
            "description": "The actual identifier value (e.g. GTIN digits \"09359502000010\", GLN \"4000001000005\"). Must match the regex pattern registered for this identifier type in the scheme configuration.",
            "example": "12345678901234",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "secondaryIdentifiersPath",
            "required": true,
            "in": "path",
            "allowEmptyValue": true,
            "description": "Qualifier path for sub-identification (e.g. \"10/LOT123\" for batch/lot, \"21/SER456\" for serial number). May be empty for resolution without qualifiers. Implements IDR-09 logical grouping, allowing different link targets for different batches or serial numbers of the same product. Multiple qualifiers can be chained (e.g. \"10/LOT123/21/SER456\").",
            "example": "10/12345678901234567890",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "linkType",
            "required": false,
            "in": "query",
            "description": "Filter by link relation type. Use UNTP-defined types (e.g. \"untp:dpp\" for Digital Product Passport, \"untp:dcc\" for Digital Conformity Credential, \"untp:dfr\" for Digital Facility Record, \"untp:dte\" for Digital Traceability Event) or scheme-specific types (e.g. \"gs1:certificationInfo\"). Set to \"all\" to return the full RFC 9264 linkset containing all registered link variants. Implements IDR-07.",
            "example": "all",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "accessRole",
            "required": false,
            "in": "query",
            "description": "Variant-based disclosure filtering per IDR-10 (secure targets). Filters resolution results to only include link variants whose accessRole array includes the specified role. Standard UNTP roles: anonymous (default, public access), customer (supply chain partner), regulator (government authority), recycler (end-of-life processor), auditor (conformity assessment body), owner (identifier registrant).",
            "example": "customer",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "decryptionKey",
            "required": false,
            "in": "query",
            "description": "Shared secret for decrypting encrypted content at the target URL. When the matched link variant has fwqs (forward query string) enabled, this key is appended as a query parameter to the target URL on redirect. Supports encrypted credential retrieval per IDR-10 secure targets. Silently ignored when fwqs is disabled for the matched link or when resolution returns a linkset rather than a redirect.",
            "example": "a3f2b8c1d5e6f7a8b9c0d1e2f3a4b5c6",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the resolved default link target URL. The Location header contains the target URL. When fwqs is enabled on the matched link variant, query parameters from the resolution request (e.g. decryptionKey) are appended to the target URL."
          },
          "400": {
            "description": "Identifier path does not match registered scheme patterns. The namespace is not registered, the identifierKeyType is not a valid application identifier within the namespace, or the identifierKey does not match the registered regex pattern.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FieldErrorsResponse"
                }
              }
            }
          },
          "404": {
            "description": "No link resolver entry found for this identifier. The namespace + identifierKeyType + identifierKey + qualifierPath combination has no registered links, or the entry exists but is inactive."
          }
        },
        "tags": [
          "Link Resolution"
        ]
      }
    },
    "/.well-known/resolver": {
      "get": {
        "operationId": "CommonController_getResolver",
        "summary": "Resolver Description File",
        "description": "Returns the Resolver Description File per ISO/IEC 18975 section 7 and IETF RFC 8615 (well-known URIs). Declares the resolver's supported identifier namespaces, capabilities, and conformance level. Clients SHOULD check this endpoint before assuming a domain hosts an IDR service. Implements IDR-14 (resolver discovery). This endpoint is public and does not require authentication.",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Resolver Description File returned successfully. Contains the resolver's supported namespaces, conformance level, and service metadata per ISO/IEC 18975 section 7."
          }
        },
        "tags": [
          "Link Resolution"
        ]
      }
    },
    "/voc": {
      "get": {
        "operationId": "CommonController_getVoc",
        "summary": "List supported link type vocabularies",
        "description": "Returns the resolver's supported link type vocabulary. When show=linktypes, lists all registered link types with their definitions, titles, and namespace prefixes. The prefix parameter filters by vocabulary namespace — for example, \"untp\" returns only UNTP-defined link types (dpp, dcc, dfr, dte, idr), while \"gs1\" returns GS1-defined types (certificationInfo, hasRetailers, etc.). This endpoint is public and does not require authentication.",
        "parameters": [
          {
            "name": "show",
            "required": false,
            "in": "query",
            "description": "Set to \"linktypes\" to retrieve the full link type vocabulary. When omitted, returns a summary of available vocabulary categories.",
            "schema": {
              "type": "string",
              "example": "linktypes"
            }
          },
          {
            "name": "prefix",
            "required": false,
            "in": "query",
            "description": "Filter link types by vocabulary namespace prefix (e.g. \"untp\" for UNTP-defined types, \"gs1\" for GS1 types). Only applies when show=linktypes. When omitted, all link types across all namespaces are returned.",
            "schema": {
              "type": "string",
              "example": "gs1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Link type vocabulary returned successfully. When show=linktypes, returns an array of link type definitions with their namespace, shortcode, title, and description."
          }
        },
        "tags": [
          "Link Resolution"
        ]
      }
    },
    "/voc/{linktype}": {
      "get": {
        "operationId": "CommonController_getLinkType",
        "summary": "Get link type details",
        "description": "Returns the detailed definition of a specific link type, including its namespace, title, description, and any associated metadata. Use this to look up the semantics of a link type encountered in a linkset response. This endpoint is public and does not require authentication.",
        "parameters": [
          {
            "name": "linktype",
            "required": true,
            "in": "path",
            "description": "The link type identifier to look up (e.g. \"gs1:certificationInfo\", \"untp:dpp\"). Must match a link type registered in the resolver's vocabulary.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Link type definition returned successfully."
          },
          "404": {
            "description": "The specified link type is not registered in this resolver's vocabulary."
          }
        },
        "tags": [
          "Link Resolution"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "type": "http",
        "description": "JWT bearer token for authenticated management operations (link registration, link management, identifier scheme management). Resolution endpoints (link resolution, /.well-known/resolver, vocabulary) are public and do not require authentication. Token issuance and claims are implementation-specific — the resolver operator defines the authentication provider and authorization model."
      }
    },
    "schemas": {
      "ApplicationIdentifier": {
        "type": "object",
        "description": "Defines a single identifier type within an identifier scheme. Application identifiers can be primary identifiers (type \"I\") that form the main resolution key, qualifiers (type \"Q\") that provide sub-identification such as batch or serial number, or data attributes (type \"D\") that carry additional information.",
        "properties": {
          "title": {
            "type": "string",
            "description": "Human-readable name for this identifier type (e.g. \"Global Trade Item Number (GTIN)\", \"Batch or Lot Number\"). Displayed in management interfaces and documentation.",
            "example": "Global Trade Item Number (GTIN)"
          },
          "label": {
            "type": "string",
            "description": "Short display label for compact UI rendering (e.g. \"GTIN\", \"BATCH/LOT\", \"NLISID\"). Typically an abbreviation or acronym.",
            "example": "GTIN"
          },
          "shortcode": {
            "type": "string",
            "description": "URL-friendly identifier used in resolution paths (e.g. \"gtin\", \"lot\", \"nlisid\"). For GS1 schemes, the shortcode typically maps to a human-readable form of the Application Identifier. This value appears in the identifierKeyType segment of the resolution URL path.",
            "example": "gtin"
          },
          "ai": {
            "type": "string",
            "description": "ISO/IEC 15459 Application Identifier code (e.g. \"01\" for GTIN, \"10\" for batch/lot). For GS1 schemes, this is the numeric AI code. For non-GS1 schemes, this field is optional and defaults to the shortcode value if omitted. Used internally for mapping between numeric AI codes and human-readable shortcodes.",
            "example": "01"
          },
          "type": {
            "type": "string",
            "description": "Classification of this application identifier. \"I\" = primary Identifier (forms the main resolution key), \"Q\" = Qualifier (provides sub-identification such as batch, serial number, or lot), \"D\" = Data attribute (carries additional information but does not participate in resolution path construction). Primary identifiers and qualifiers appear in the URL path; data attributes do not.",
            "example": "I",
            "enum": [
              "I",
              "Q",
              "D"
            ]
          },
          "regex": {
            "type": "string",
            "description": "Regular expression validation pattern for the identifier value. Resolution rejects identifiers that do not match this pattern with a 400 error. Patterns should be anchored and specific to prevent false matches (e.g. \"(\\\\d{12,14}|\\\\d{8})\" for GTIN).",
            "example": "\\d{12,14}|\\d{8}"
          },
          "format": {
            "type": "string",
            "description": "Human-readable format descriptor (e.g. \"N14\" for 14 numeric digits, \"X..20\" for up to 20 alphanumeric characters). Informational only — validation is performed using the regex field.",
            "example": "X..20"
          },
          "qualifiers": {
            "description": "List of qualifier AI codes that may appear after this primary identifier in the resolution path. Only applicable when type is \"I\". For example, a GTIN (AI 01) might list [\"10\", \"21\", \"22\"] to indicate that batch/lot (10), serial (21), and consumer product variant (22) qualifiers are permitted.",
            "example": [
              "22",
              "10",
              "21"
            ],
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "title",
          "label",
          "shortcode",
          "type",
          "regex"
        ]
      },
      "IdentifierDto": {
        "type": "object",
        "description": "Configuration for an identifier scheme (namespace). Defines the set of identifier types that may be resolved under this namespace and their validation rules. Must be registered before any link registration can reference this namespace.",
        "properties": {
          "namespace": {
            "type": "string",
            "description": "Unique name for the identifier scheme (e.g. \"gs1\", \"nlis\", \"integrity-systems\"). Must match the scheme's registration in the UNTP IDR register. This value appears as the first path segment in resolution URLs.",
            "example": "example-identifier-scheme"
          },
          "namespaceURI": {
            "type": "string",
            "description": "Optional URI providing an authoritative reference for the identifier scheme (e.g. the scheme owner's domain or specification URL). Informational — used for documentation and provenance, not for resolution.",
            "example": ""
          },
          "namespaceProfile": {
            "type": "string",
            "description": "Optional profile identifier for scheme-specific resolver configuration. Allows a single namespace to support multiple resolution profiles (e.g. different validation rules or link type vocabularies for different use cases).",
            "example": ""
          },
          "applicationIdentifiers": {
            "description": "The set of identifier types supported within this scheme. Must include at least one primary identifier (type \"I\"). Qualifiers and data attributes are optional.",
            "example": [
              {
                "title": "Global Trade Item Number (GTIN)",
                "label": "GTIN",
                "shortcode": "gtin",
                "ai": "01",
                "type": "I",
                "qualifiers": [
                  "10"
                ],
                "regex": "(\\d{12,14}|\\d{8})"
              },
              {
                "title": "Batch or lot number",
                "label": "BATCH/LOT",
                "shortcode": "lot",
                "ai": "10",
                "type": "Q",
                "regex": "([\\x21-\\x22\\x25-\\x2F\\x30-\\x39\\x41-\\x5A\\x5F\\x61-\\x7A]{0,20})"
              }
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApplicationIdentifier"
            }
          }
        },
        "required": [
          "namespace",
          "applicationIdentifiers"
        ]
      },
      "Response": {
        "type": "object",
        "description": "A single link variant within a resolver entry. Each response represents one combination of link type, language, MIME type, geographic context, and access role — per UNTP IDR-06 (one carrier, many links). Multiple responses on the same identifier allow different stakeholders to discover different credentials in different formats and languages.",
        "properties": {
          "linkId": {
            "type": "string",
            "description": "Server-generated unique identifier for this link variant (UUID format). Read-only — assigned on creation and used for subsequent GET, PUT, and DELETE operations on this specific link.",
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "readOnly": true
          },
          "linkType": {
            "type": "string",
            "description": "IANA or scheme-specific link relation type. UNTP defines: \"untp:dpp\" (Digital Product Passport), \"untp:dcc\" (Digital Conformity Credential), \"untp:dfr\" (Digital Facility Record), \"untp:dte\" (Digital Traceability Event), \"untp:idr\" (Identity Resolver). Scheme-specific types use the {namespace}:{type} convention (e.g. \"gs1:certificationInfo\"). Implements IDR-07.",
            "example": "example-identifier-scheme:certificationInfo"
          },
          "rel": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Additional IANA or extension link relation types that qualify this link beyond its primary linkType — for example \"edit\" or \"latest-version\". Optional. Note: \"predecessor-version\" is reserved — the resolver derives it automatically from version history when a link variant is replaced and SHOULD NOT be set explicitly by publishers.",
            "example": [
              "edit"
            ]
          },
          "title": {
            "type": "string",
            "description": "Human-readable title for this link variant, displayed in linkset responses and management interfaces (e.g. \"Certification Information\", \"Digital Product Passport (English)\").",
            "example": "Certification Information"
          },
          "targetUrl": {
            "type": "string",
            "description": "The URL that resolution redirects to or includes in the linkset. For UNTP credentials, this is typically the URL where the Verifiable Credential can be retrieved. When fwqs is enabled, query parameters from the resolution request are appended to this URL.",
            "example": "https://example.com"
          },
          "mimeType": {
            "type": "string",
            "pattern": "\\w+/[-+.\\w]+",
            "description": "Media type of the target resource — any well-formed MIME type per RFC 6838 is accepted. For UNTP verifiable credentials, use \"application/vc+ld+json\" (W3C VC 2.0). Other common values: \"application/json\", \"application/pdf\" for human-readable documents, \"application/linkset+json\" for embedded linksets. Implements content negotiation per IDR-08.",
            "example": "application/json"
          },
          "hreflang": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "(^\\w{2}$)|(^\\w{2}-\\w{2}$)"
            },
            "description": "BCP 47 language tags supported by the resource at the target URL. Content negotiation is handled at the target — a single targetUrl may return, for example, English, French, or German variants of the same resource based on the request's Accept-Language header, in which case hreflang would be [\"en\", \"fr\", \"de\"]. Consumers use this list to know which languages they can request from the target before issuing the call. Implements IDR-08 language negotiation.",
            "example": [
              "en"
            ]
          },
          "context": {
            "type": "string",
            "description": "Geographic or situational context code (e.g. \"au\" for Australia, \"eu\" for European Union, \"us\" for United States). Implements IDR-08 context negotiation — allows different link targets for different jurisdictions. For example, a product may have different conformity credentials for different regulatory contexts.",
            "example": "au"
          },
          "defaultLinkType": {
            "type": "boolean",
            "description": "When true, this variant is the default for its link type dimension. Resolution returns this variant when no linkType filter is specified in the request. Exactly one response per identifier SHOULD be marked as the default link type.",
            "example": true
          },
          "defaultMimeType": {
            "type": "boolean",
            "description": "When true, this variant is the default for its MIME type dimension. Resolution returns this variant when multiple MIME types are available and no preference is specified.",
            "example": true
          },
          "defaultContext": {
            "type": "boolean",
            "description": "When true, this variant is the default for its context dimension. Resolution returns this variant when no geographic/situational context is specified.",
            "example": true
          },
          "fwqs": {
            "type": "boolean",
            "description": "Forward Query String. When true, query parameters from the resolution request (e.g. decryptionKey) are appended to the targetUrl on redirect. Essential for encrypted credential retrieval per IDR-10 — the decryption key is passed through to the credential host without being stored by the resolver.",
            "example": false
          },
          "active": {
            "type": "boolean",
            "description": "Whether this link variant is live and included in resolution responses. Inactive links are excluded from resolution but retained in storage. Set to false for soft-delete or to temporarily disable a link variant.",
            "example": true
          },
          "encryptionMethod": {
            "type": "string",
            "description": "Encryption algorithm applied to the target resource. \"none\" indicates the target is unencrypted (public access). \"AES-128\" or \"AES-256\" indicate the credential at the target URL is encrypted and requires a decryptionKey to access. Implements IDR-10 secure targets for confidential credentials.",
            "enum": [
              "none",
              "AES-128",
              "AES-256"
            ],
            "example": "none"
          },
          "accessRole": {
            "type": "array",
            "description": "UNTP access roles permitted to retrieve this link variant. Implements variant-based disclosure filtering per IDR-10. During resolution, only variants whose accessRole array includes the requested role (or \"untp:accessRole#Anonymous\" for unauthenticated requests) are returned. Multiple roles can be assigned to a single variant.",
            "example": [
              "untp:accessRole#Anonymous"
            ],
            "items": {
              "type": "string",
              "enum": [
                "untp:accessRole#Anonymous",
                "untp:accessRole#Customer",
                "untp:accessRole#Regulator",
                "untp:accessRole#Recycler",
                "untp:accessRole#Auditor",
                "untp:accessRole#Owner"
              ]
            }
          },
          "public": {
            "type": "boolean",
            "description": "Whether the link itself is safe to share publicly (e.g. publish in directories, broadcast on product packaging). Distinct from accessRole, which controls who can retrieve the resource at the URL — a link with public: true and accessRole: [\"untp:accessRole#Customer\"] is safe to advertise, but only customers can retrieve the underlying resource. Optional; if omitted, publishers SHOULD assume the link is not safe to share.",
            "example": true
          },
          "method": {
            "type": "string",
            "description": "HTTP method for accessing the target resource. \"GET\" for retrieval of existing credentials. \"POST\" for creating new linked resources (e.g. submitting traceability events or conformity credentials back to the identifier owner). Implements IDR-10 edit links, enabling bidirectional data flows in supply chains.",
            "example": "POST"
          }
        },
        "required": [
          "defaultLinkType",
          "defaultMimeType",
          "fwqs",
          "active",
          "linkType",
          "title",
          "targetUrl",
          "mimeType",
          "hreflang",
          "context",
          "defaultContext"
        ]
      },
      "CreateLinkRegistrationDto": {
        "type": "object",
        "description": "Registers a link resolver entry per IDR-02 (one carrier, many links). Associates a specific identifier — defined by namespace, identificationKeyType, identificationKey, and optional qualifierPath — with one or more typed link targets. Each entry in the responses array represents a link variant that can be discovered through resolution.",
        "properties": {
          "namespace": {
            "type": "string",
            "description": "Must reference a previously registered identifier scheme (see POST /identifiers). The namespace identifies which scheme this identifier belongs to (e.g. \"gs1\", \"nlis\").",
            "example": "example-identifier-scheme"
          },
          "identificationKeyType": {
            "type": "string",
            "description": "Must be a registered shortcode within the specified namespace (e.g. \"gtin\" for GS1 GTIN, \"nlisid\" for NLIS). Determines which application identifier type this entry is for.",
            "example": "gtin"
          },
          "identificationKey": {
            "type": "string",
            "description": "The specific identifier value being registered (e.g. \"09359502000010\" for a GTIN). Must match the regex pattern configured for the identificationKeyType in the namespace's scheme definition.",
            "example": "12345678901234"
          },
          "description": {
            "type": "string",
            "description": "Human-readable description of the identified item (e.g. \"Recycled Copper Cathode — 99.99% purity\"). Included in linkset responses to help consumers understand what the identifier refers to.",
            "example": "Product description"
          },
          "itemDescription": {
            "type": "string",
            "description": "Deprecated: Use \"description\" instead. If both are provided, \"description\" takes precedence.",
            "example": "Product description",
            "deprecated": true
          },
          "qualifierPath": {
            "type": "string",
            "description": "Path segment for qualifiers providing sub-identification (e.g. \"/10/LOT123\" for a specific batch, \"/21/SER456\" for a serial number). Use \"/\" for the base identifier without qualifiers. Implements IDR-09 logical grouping — different qualifier paths on the same identifier can have different link targets.",
            "example": "/10/12345678901234567890"
          },
          "active": {
            "type": "boolean",
            "description": "Whether this resolver entry is live. When false, the identifier returns 404 on resolution even if link variants exist. Useful for pre-registering links before they should be publicly discoverable.",
            "example": true
          },
          "responses": {
            "description": "Array of link targets (variants). Each entry represents one link variant per IDR-06 — a unique combination of link type, language, MIME type, geographic context, and access role. For a typical UNTP product, this might include a DPP credential (JSON), a conformity certificate (PDF), and a human-readable product page (HTML), each potentially in multiple languages.",
            "example": [
              {
                "defaultLinkType": true,
                "defaultMimeType": true,
                "defaultContext": true,
                "fwqs": false,
                "active": true,
                "linkType": "example-identifier-scheme:certificationInfo",
                "rel": [
                  "edit"
                ],
                "hreflang": [
                  "en"
                ],
                "context": "au",
                "title": "Certification Information",
                "targetUrl": "https://example.com",
                "mimeType": "application/json",
                "encryptionMethod": "none",
                "accessRole": [
                  "untp:accessRole#Anonymous"
                ],
                "public": true,
                "method": "POST"
              }
            ],
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Response"
            }
          }
        },
        "required": [
          "namespace",
          "identificationKeyType",
          "identificationKey",
          "description",
          "qualifierPath",
          "active",
          "responses"
        ]
      },
      "FieldError": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "example": "identificationKeyType"
          },
          "message": {
            "type": "string",
            "example": "Identification key type 'invalid_key_type' is not registered with the namespace 'example-identifier-scheme'"
          }
        },
        "required": [
          "field",
          "message"
        ]
      },
      "FieldErrorsResponse": {
        "type": "object",
        "properties": {
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            }
          }
        },
        "required": [
          "errors"
        ]
      },
      "UpdateLinkDto": {
        "type": "object",
        "description": "Partial update for a single link variant. All fields are optional — only the fields included in the request body are modified. Omitted fields retain their current values. Use this to rotate target URLs, update access roles, toggle active status, or change encryption methods without affecting other properties.",
        "properties": {
          "targetUrl": {
            "type": "string",
            "description": "The URL that resolution redirects to or includes in the linkset. Update this to rotate credential hosting URLs or point to a new version of the target resource.",
            "example": "https://example.com"
          },
          "linkType": {
            "type": "string",
            "description": "IANA or scheme-specific link relation type (e.g. \"untp:dpp\", \"gs1:certificationInfo\"). Changing the link type changes how this variant is classified in resolution and linkset responses.",
            "example": "example-identifier-scheme:certificationInfo"
          },
          "rel": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Additional IANA or extension link relation types that qualify this link beyond its primary linkType. Replaces the existing rel array (does not merge). \"predecessor-version\" is reserved — see Response schema.",
            "example": [
              "edit"
            ]
          },
          "title": {
            "type": "string",
            "description": "Human-readable title for this link variant.",
            "example": "Certification Information"
          },
          "mimeType": {
            "type": "string",
            "pattern": "\\w+/[-+.\\w]+",
            "description": "Media type of the target resource — any well-formed MIME type per RFC 6838 is accepted (e.g. \"application/vc+ld+json\", \"application/json\", \"application/pdf\", \"application/linkset+json\").",
            "example": "application/json"
          },
          "hreflang": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "(^\\w{2}$)|(^\\w{2}-\\w{2}$)"
            },
            "description": "BCP 47 language tags supported by the resource at the target URL — content negotiation is handled at the target.",
            "example": [
              "en"
            ]
          },
          "context": {
            "type": "string",
            "description": "Geographic or situational context code (e.g. \"au\", \"eu\", \"us\").",
            "example": "au"
          },
          "active": {
            "type": "boolean",
            "description": "Whether this link variant is live and included in resolution responses. Set to false to deactivate without deleting."
          },
          "fwqs": {
            "type": "boolean",
            "description": "Forward Query String. When true, query parameters from the resolution request are appended to the targetUrl on redirect."
          },
          "defaultLinkType": {
            "type": "boolean",
            "description": "When true, this variant is the default for its link type dimension."
          },
          "defaultContext": {
            "type": "boolean",
            "description": "When true, this variant is the default for its context dimension."
          },
          "defaultMimeType": {
            "type": "boolean",
            "description": "When true, this variant is the default for its MIME type dimension."
          },
          "encryptionMethod": {
            "type": "string",
            "description": "Encryption algorithm applied to the target resource. \"none\" for unencrypted, \"AES-128\" or \"AES-256\" for encrypted credentials requiring a decryptionKey.",
            "enum": [
              "none",
              "AES-128",
              "AES-256"
            ],
            "example": "none"
          },
          "accessRole": {
            "type": "array",
            "description": "UNTP access roles permitted to retrieve this link variant. Updates the full role list (replaces, does not merge).",
            "example": [
              "untp:accessRole#Anonymous"
            ],
            "items": {
              "type": "string",
              "enum": [
                "untp:accessRole#Anonymous",
                "untp:accessRole#Customer",
                "untp:accessRole#Regulator",
                "untp:accessRole#Recycler",
                "untp:accessRole#Auditor",
                "untp:accessRole#Owner"
              ]
            }
          },
          "public": {
            "type": "boolean",
            "description": "Whether the link itself is safe to share publicly. Distinct from accessRole — see Response schema for full semantics."
          },
          "method": {
            "type": "string",
            "description": "HTTP method for accessing the target. \"GET\" for retrieval, \"POST\" for creating new linked resources.",
            "example": "POST"
          }
        }
      }
    }
  }
}