Migration guide
EBSI API Migration Guide, from current pilot APIs to EBSI Core API v1.
This guide maps the current API (nine separately-versioned services) to the new API (a single unified EBSI Core API v1).
The headline change: every write operation now goes through one directly-accessible
JSON-RPC endpoint POST /v1/ledger instead of the per-service …/jsonrpc
transaction preparation endpoints.
1. What changed at a glance
| Area | Old | New |
|---|---|---|
| API surface | 9 APIs, each with its own OpenAPI doc and version | One API: EBSI Core API v1 (1.0.0) |
| Base URL | https://api-<env>.ebsi.<node operator's domain> + per-service version segment | https://api-<env>.ebsi.<node operator's domain>/v1, with the node picked from the Trusted Nodes List |
| Path version | /did-registry/v5/…, /authorisation/v4/…, /timestamp/v4/… | single /v1 at the base, no per-service version |
| Path names | /authorisation/v4/…, /trusted-*-registry/… | /auth/…, /*-registry/… (trusted- prefix dropped) |
| Writes | POST /<service>/<v>/jsonrpc transaction preparation methods + sendSignedTransaction | POST /v1/ledger (eth_sendRawTransaction) |
| Besu read access | POST /ledger/v4/blockchains/besu | POST /v1/ledger |
| Contract instances | one deployment per registry | multiple beacon-proxy instances. Target one via the contractAddress read param or a scope:<0x-address> token suffix. GET /v1/<registry>/details lists a registry instance's on-chain links. Detailed in §4, Targeting a contract instance. |
| DID writes | granular methods (updateBaseDocument, addVerificationMethod, …) via /did-registry/v5/jsonrpc | same granular method names, sent as eth_sendRawTransaction calldata via /v1/ledger. |
| Scopes | openid didr_write (underscore) | openid did-registry:write (colon, full registry name), combinable, instance-targetable, holder-DID-method-restricted |
| Token grant | OpenID4VP draft 14 (Presentation Exchange with presentation_submission) | OpenID4VP 1.0 final (DCQL) only. Draft 14 is removed. Detailed in §4, Requesting a token. |
| Pagination | page[after] = integer page number | page[after] = integer page number, 1-based (unchanged) |
| Docs path | /api/<service> | /api/core/<service> |
2. Base URL and path structure
<node operator's domain> is the domain of an approved node, listed in the Trusted Nodes List.
Current
https://api-<env>.ebsi.<node operator's domain>/did-registry/v5/identifiers/{did}
https://api-<env>.ebsi.<node operator's domain>/authorisation/v4/token
https://api-<env>.ebsi.<node operator's domain>/trusted-issuers-registry/v5/issuers
New
https://api-<env>.ebsi.<node operator's domain>/v1/did-registry/identifiers/{did}
https://api-<env>.ebsi.<node operator's domain>/v1/auth/token
https://api-<env>.ebsi.<node operator's domain>/v1/issuers-registry/issuers
Service path renames:
| Current | New |
|---|---|
/authorisation/v4 | /auth |
/did-registry/v5 | /did-registry |
/ledger/v4 | /ledger |
/timestamp/v4 | /timestamp |
/track-and-trace/v1 | /track-and-trace |
/trusted-contracts-registry/v1 | /contracts-registry |
/trusted-issuers-registry/v5 | /issuers-registry |
/trusted-policies-registry/v3 | /policies-registry |
/trusted-schemas-registry/v3 | /schemas-registry |
3. Write operations and the new Ledger JSON-RPC endpoint
Current model: per service, 2 calls + local signing
1. POST /<service>/<v>/jsonrpc Authorization: Bearer <token: *_write / *_invite>
{ "jsonrpc":"2.0", "method":"insertDidDocument", "params":[ … ], "id":1 }
Response: 200 { "result": <unsigned Ethereum transaction> }
2. sign the transaction locally
3. POST /<service>/<v>/jsonrpc
{ "jsonrpc":"2.0", "method":"sendSignedTransaction", "params":[ … ], "id":2 }
Response: 200 { "result": "<txHash>" }
Transaction preparation methods lived on each service: insertDidDocument, addVerificationMethod,
setAttributeData, addIssuerProxy, insertPolicy, insertSchema, timestampHashes,
createDocument, grantAccess where each returned an unsigned transaction.
Allow-listed contract calls went to POST /ledger/v4/blockchains/besu
(eth_sendRawTransaction, scope openid ledger_invoke).
New model one endpoint: POST /v1/ledger
POST /v1/ledger is the Besu JSON-RPC interface, exposed directly.
- Read methods (
eth_getBlockByNumber,eth_getTransactionByHash,eth_getTransactionReceipt,eth_call,eth_chainId,eth_estimateGas, …) no authentication, forwarded straight to the Besu node. - Write =
eth_sendRawTransactionsend a client-built, client-signed raw transaction withAuthorization: Bearer <access_token>. There are no more server-side transaction preparation endpoints. Encode the calldata yourself from the contract ABI (GET /v1/<registry>/abithe default contract address is now published in each/abiresponse, and the on-chain links between registry contracts viaGET /v1/<registry>/details) with any Ethereum library, then sign and submit. - The access token scope is validated against the target registry and contract function before the transaction is forwarded.
- One token may combine several
*:writescopes the Ledger routes each transaction to the registry its function belongs to. If the matching scope is absent the entry is rejected with aJsonRpcErrornaming the missing scope. - Batch requests (JSON array, ≤ 1024 entries) and notifications (defined by missing
id) are supported. - Errors are JSON-RPC error objects:
-32700parse error is returned at HTTP400-32600(invalid request / missing scope / missing or invalid access token),-32601(method not found) and-32005(batch too large) are returned at HTTP200. A missing Bearer token on a write is a JSON-RPC-32600at HTTP200, not an HTTP401. The endpoint also answers405(non-POST, with anAllowheader) and406(unsatisfiableAccept) see §5, which now applies API-wide.
POST /v1/ledger Authorization: Bearer <token: did-registry:write>
{ "jsonrpc":"2.0", "method":"eth_sendRawTransaction", "params":["0x<signed tx>"], "id":1 }
Response: 200 { "result": "<txHash>" }
Then poll eth_getTransactionReceipt on the same endpoint and check status
(0x1 success, 0x0 reverted).
Scope for allowed on-chain functions (write via /v1/ledger)
| Scope | Registry | Allowed functions |
|---|---|---|
openid did-registry:invite | DID Registry | insertDidDocument |
openid did-registry:write | DID Registry | addController, addVerificationMethod, addVerificationRelationship, expireVerificationMethod, revokeController, revokeVerificationMethod, rollVerificationMethod, updateBaseDocument |
openid issuers-registry:invite / openid issuers-registry:write | Issuers Registry | addIssuerProxy, removeIssuerProxy, updateIssuerProxy, setAttributeData, setAttributeMetadata |
openid policies-registry:write | Policies Registry | insertPolicy, updatePolicy, activatePolicy, deactivatePolicy, insertUserAttributes, deleteUserAttributes |
openid schemas-registry:write | Schemas Registry | insertSchema, updateSchema, updateMetadata |
openid timestamp:write | Timestamp | commitTimestampHashes, timestampHashes, timestampRecordHashes, timestampVersionHashes, timestampRecordVersionHashes, appendRecordVersionHashes, detachRecordVersionHash, insertRecordVersionInfo, insertRecordOwner, revokeRecordOwner, insertHashAlgorithm, updateHashAlgorithm |
openid track-and-trace:authorise | Track and Trace | authoriseDid |
openid track-and-trace:create | Track and Trace | commitCreateDocument, createDocument |
openid track-and-trace:write | Track and Trace | grantAccess, revokeAccess, writeEvent, removeDocument |
openid ledger:invoke | Trusted Contracts | any function on an allow-listed contract |
DID Registry note: the DID Registry keeps the same granular method names as the current pilot transaction-preparation API (
updateBaseDocument,addVerificationMethod,addVerificationRelationship,revokeVerificationMethod,expireVerificationMethod,rollVerificationMethod,addController,revokeController). Two specifics:
insertDidDocument(registering a DID's initial verification method) needs adid-registry:invitetoken, and the DID Registry only accepts apublicKeywhose RFC 7638 thumbprint matches the token'scnf.jktclaim, adid-registry:invitetoken cannot register any other key (see theaccess_token_responseschema).addVerificationMethodandrollVerificationMethodrequirevMethodIdto be the RFC 7638 SHA-256 thumbprint ofpublicKey.Treat the ABI from
GET /v1/did-registry/abias the source of truth for signatures.
Commit-reveal: Timestamp hashes and Track & Trace createDocument
Two write flows are now two transactions, not one:
- Every Timestamp function that timestamps hashes (
timestampHashes,timestampRecordHashes,timestampVersionHashes,timestampRecordVersionHashes,appendRecordVersionHashes) is the reveal leg and takes a trailingsaltargument. It must be preceded, from the same sender and at leastTIMESTAMP_COMMITMENT_MATURITYblocks earlier (currently 5), by acommitTimestampHashestransaction carrying the commitment returned by the contract'scomputeTimestampCommitmentview for those exact arguments. createDocument(Track & Trace, both overloads) is likewise a reveal leg with a trailingsalt, preceded bycommitCreateDocumentat leastCREATE_DOCUMENT_COMMITMENT_MATURITYblocks earlier (currently 5) from the same sender, carryingcomputeCreateDocumentCommitment's output.
Both legs use the same scope (timestamp:write / track-and-trace:create) and are each submitted as
their own eth_sendRawTransaction. Revealing without a matured commitment reverts
(CommitmentUnknown, CommitmentNotMatured). Keep the salt secret until the reveal
that is what prevents a third party from front-running the timestamp or the document
creation.
4. Authorisation and scopes
- "Authorisation API v4" is now "Authorisation Service" "Trusted X Registry" is now "X Registry" throughout.
- Token lifetime unchanged: 2 hours, no refresh tokens.
- JWKS is unchanged apart from the
/authpath. The discovery document is corrected, and the token flow uses OpenID4VP 1.0 (final) only. See Requesting a token below. - Breaking: the OpenID4VP draft 14 grant is removed.
GET /auth/presentation-definitionsno longer exists, and the token endpoint only accepts the OpenID4VP 1.0 (final) grant, which uses a DCQL query in place of a Presentation Definition. Clients must migrate. - Breaking: the discovery document is corrected to match the actual behaviour of the Authorisation Service. See Discovery document changes below.
Scope separator: from _ to : + full names
| Old | New |
|---|---|
openid didr_invite | openid did-registry:invite |
openid didr_write | openid did-registry:write |
openid tir_invite | openid issuers-registry:invite |
openid tir_write | openid issuers-registry:write |
openid tpr_write | openid policies-registry:write |
openid tsr_write | openid schemas-registry:write |
openid timestamp_write | openid timestamp:write |
openid tnt_authorise | openid track-and-trace:authorise |
openid tnt_create | openid track-and-trace:create |
openid tnt_write | openid track-and-trace:write |
openid ledger_invoke | openid ledger:invoke |
New scope capabilities
-
Combinable scopes
did-registry:write,issuers-registry:write,timestamp:write,track-and-trace:create,track-and-trace:write,policies-registry:write,schemas-registry:writemay be requested together in a single token (e.g.openid did-registry:write issuers-registry:write) the holder must satisfy every requested scope. The onboarding/invite scopes (did-registry:invite,issuers-registry:invite,track-and-trace:authorise) andledger:invokemust still be requested alone. -
Instance targeting any scope may be suffixed with
:<0x-address>to target a specific deployed contract instance (e.g.openid track-and-trace:write:0x61c36a8d610163660E21a8b7359e1Cac0C9133e1) omitted uses the environment default. See Targeting a contract instance below. -
Allowed holder DID methods each scope only accepts a Verifiable Presentation signed by a holder DID that uses one of that scope's permitted DID methods:
did:ebsionly:did-registry:invite,did-registry:write,issuers-registry:invite,issuers-registry:write,timestamp:write,track-and-trace:authorise,track-and-trace:create,policies-registry:write,schemas-registry:write.did:ebsiordid:key:ledger:invoke,track-and-trace:write.
When scopes are combined, the holder's DID method must be allowed by every requested scope (the intersection) so
openid track-and-trace:writealone accepts adid:keyholder, butopenid did-registry:write track-and-trace:writedoes not.
Requesting a token
POST /v1/auth/token accepts the OpenID4VP 1.0 (final) grant only: grant_type=vp_token, the
/auth/token URL and the same scopes as before. The table below maps the removed draft 14 grant to
its replacement.
| Removed (OpenID4VP draft 14) | Current (OpenID4VP 1.0 final) | |
|---|---|---|
| Requirements endpoint | GET /auth/presentation-definitions?scope=… | GET /auth/dcql-queries?scope=… |
| Discovery claim | presentation_definition_endpoint | dcql_query_endpoint |
| Query language | Presentation Exchange (input_descriptors) | DCQL (credentials) |
vp_token | compact-JWS Verifiable Presentation JWT | JSON object keyed by credential-query identifier, e.g. {"self_attestation_credential": ["eyJ…"]} |
presentation_submission | required | must be omitted |
| Scopes requiring no credential | present an empty Verifiable Presentation (input_descriptors: []) | present a self-issued SelfAttestation credential |
A compact-JWS vp_token is rejected with invalid_request, with the message vp_token: must be a
JSON object keyed by DCQL credential-query id (OpenID4VP 1.0); the legacy draft 14 compact-JWS form
is no longer supported.
Signing algorithms of the presentation are defined per scope. did-registry:invite and
track-and-trace:authorise accept ES256K only, and GET /auth/dcql-queries advertises the
algorithm that applies to the requested scope. All other scopes accept ES256 and ES256K.
Credentials accept ES256 and ES256K for every scope. This represents a widening relative to
the pilot API, in which credentials accepted ES256 only and a combined scope request resolved to
the intersection of the requested scopes.
Migration is required. The principal behavioural difference concerns the seven combinable
scopes (did-registry:write, issuers-registry:write, timestamp:write,
track-and-trace:create, track-and-trace:write, policies-registry:write,
schemas-registry:write). DCQL requires credentials to be non-empty, so whereas the removed
grant accepted an empty presentation, the current grant expects a single shared, self-issued
SelfAttestation credential whose issuer and subject are both the holder's own DID. Requesting
several of these scopes together results in a single credential requirement rather than one per
scope. See Access Control for its exact structure and
constraints.
Discovery document changes
GET /auth/.well-known/openid-configuration is corrected to match the Authorisation Service's
actual behaviour. This differs deliberately from the published Authorisation API v4 discovery
document:
| Claim | Before | Now |
|---|---|---|
authorization_endpoint | present (there is no such route) | removed |
presentation_definition_endpoint | present | removed. Use dcql_query_endpoint |
id_token_signing_alg_values_supported | ["none"] | ["ES256"]. Verify the signature of ID tokens |
id_token_types_supported | subject_signed_id_token | attester_signed_id_token |
Error responses: Problem Details (RFC 9457)
- Multi-issue validation failures return the constant
detailThe request failed validation, together with a newerrorsarray of{ detail, pointer?, parameter? }items.pointeris an RFC 6901 JSON Pointer to a request body field, andparameternames a query or path parameter. Don't parsedetail. - Most problems now carry a stable
typeof the formurn:ebsi:problems:<slug>(for exampleurn:ebsi:problems:invalid-contract-address) instead ofabout:blank. The URIs are the same in every environment and are not dereferenceable. Match ontyperather thantitle. - Responses that carry only an HTTP status (malformed body, 405, 406, 503) keep
about:blank.
Targeting a contract instance
Every registry (DID Registry, Issuers Registry, Policies Registry, Schemas Registry,
Timestamp, Track & Trace) is deployed as one or more beacon-proxy instances: separate
contract addresses that share a single upgradeable implementation. Each environment has one
default instance per registry (the address printed in that registry's /abi response).
The Contracts Registry (Proxy Factory) is the on-chain registry of these instances
GET /v1/contracts-registry/contracts lists them.
The pilot API exposed only the default instance. The new API lets you address a non-default instance in two independent places:
Reads the contractAddress query parameter. Accepted on every registry read endpoint
except the seven /abi endpoints (the ABI is the same for every instance) and the three
/auth/* discovery endpoints. ?contractAddress=0x… selects the instance omitted uses the
environment default. Validation:
- not matching
^0x[0-9a-fA-F]{40}$returns400(parameter validation). - well-formed but nothing is deployed there, or the deployed contract isn't the expected
registry type returns
400Invalid Contract Address (titleInvalid Contract Address,detailsays "No contract is deployed at address …" or "… does not match the expected contract"). In the pilot this parameter was accepted and silently ignored.
Token requests the :<0x-address> scope suffix. Append it to any scope in the
/auth/token request, e.g. openid track-and-trace:write:0x61c3…. This both scopes the token's
on-chain checks to that instance and changes how the Authorisation Service resolves the DID
Registry it uses to verify the holder's Verifiable Presentation signature:
| Scope | What the suffixed address points at, and the DID Registry used for holder resolution |
|---|---|
did-registry:invite / did-registry:write | the targeted instance is the DID Registry used for holder resolution |
track-and-trace:authorise / track-and-trace:create / track-and-trace:write | the targeted Track & Trace instance its own on-chain didRegistry() and policiesRegistry() are read and used (holder resolution, and the TNT:authoriseDid policy check) |
issuers-registry:invite / issuers-registry:write | the targeted Issuers Registry instance its own on-chain linked DID Registry is used for holder resolution |
policies-registry:write / schemas-registry:write / timestamp:write | the targeted Policies / Schemas / Timestamp instance these have no linked DID Registry, so holder resolution always falls back to the environment default DID Registry |
- The address must be a currently registered beacon-proxy instance of the expected
registry type in the Contracts Registry, unless it is that registry's own environment
default. An address that resolves as the right contract type but was never deployed
through the Contracts Registry results in an
invalid_request. - The VP is signed once, so every combined scope whose eligibility is checked against a
DID Registry instance (
did-registry:*,issuers-registry:*,track-and-trace:*including one left unaddressed, which resolves to the default) must resolve to the same DID Registry. Combining an addresseddid-registry:writewith an unaddressedissuers-registry:writewhose default Issuers Registry is linked to a different DID Registry results in aninvalid_request, even though neither address on its own looks inconsistent.policies-registry:write/schemas-registry:write/timestamp:writeare exempt because their eligibility isn't rooted in any DID Registry.
contractAddress parameter ties the two together. Before issuing a token, the Authorisation Service verifies the holder's Verifiable Presentation signature, which means fetching the holder's DID document from a DID Registry. It picks the DID Registry instance using the rules above, then resolves the holder DID against it through the public GET /v1/did-registry/identifiers/{holderDid} endpoint with contractAddress set to that instance. This is the same call, and the same parameter, that any client can make directly to resolve a DID against a non-default DID Registry instance. Omitting contractAddress falls back to the environment default.
Inspecting an instance's links GET /v1/<registry>/details
A new read endpoint returns an instance's own
address plus the registry contracts it was initialized against. ?contractAddress= selects
the instance omitted uses the environment default.
| Endpoint | Response fields |
|---|---|
GET /v1/did-registry/details | contractAddress, policyRegistryContract |
GET /v1/issuers-registry/details | contractAddress, didRegistryContract, policyRegistryContract |
GET /v1/schemas-registry/details | contractAddress, policyRegistryContract |
GET /v1/timestamp/details | contractAddress, policyRegistryContract |
GET /v1/track-and-trace/details | contractAddress, didRegistryContract, policiesRegistryContract |
- DID / Issuers / Schemas / Timestamp links are fixed at
initialize()and immutable. - Track & Trace's
didRegistry/policiesRegistryare mutable (setDidRegistry/setPoliciesRegistry) and may be unset. - There is no
contracts-registry/detailsthe Contracts Registry is the instance registry useGET /v1/contracts-registry/contracts. - Same error surface as other reads:
400Invalid Contract Address,405,406,500,503.
Use it to confirm that the instances you intend to combine in one token are mutually
consistent (same DID Registry) before the request, or to find out which Policies Registry a
policies-registry:write / schemas-registry:write / timestamp:write check will actually hit.
5. Read (REST) endpoints
All current GET/HEAD endpoints keep a 1-to-1 equivalent re-pathed under /v1 with the
service renames from §2. Behaviour changes:
- Pagination is unchanged.
page[after]is still a 1-based integer page number (page[after]=1,2, …), same as the pilot API. Keep incrementing it, or followlinks.nextthe cursor is the page index. contractAddressquery parameter now actually selects which beacon-proxy instance is queried (previously accepted-but-ignored). Accepted on every registry read endpoint except the/abiand/auth/*endpoints a wrong or undeployed address gives400Invalid Contract Address, not500. Full detail in §4, Targeting a contract instance.- DID document content negotiation is retained.
GET /v1/did-registry/identifiers/{did}returns the@context-freeapplication/did+jsonrepresentation only when theAcceptheader is exactlyapplication/did+jsonany other value including*/*yieldsapplication/did+ld+json. - Response
self/linksURLs now contain/v1/and no/vN/segment. 405 Method Not Allowed(with anAllowheader) and406 Not Acceptableare documented on essentially every path.POST /auth/tokenerror bodies are now an OAuth 2.0 error object ({ "error": …, "error_description": … }) served asapplication/jsonand always with HTTP400including forserver_errorinstead ofapplication/problem+json.- New
GET /v1/<registry>/detailsreturns the registry contracts an instance is on-chain linked to. No pilot equivalent detailed in §4, Inspecting an instance's links.
Endpoint map for reads
| Service | Current (…ebsi.<node operator's domain> + path) | New (…ebsi.<node operator's domain>/v1 + path) |
|---|---|---|
| Authorisation | /authorisation/v4/.well-known/openid-configuration | /auth/.well-known/openid-configuration |
/authorisation/v4/jwks | /auth/jwks | |
/authorisation/v4/presentation-definitions | /auth/presentation-definitions | |
| (none) | /auth/dcql-queries (new) | |
POST /authorisation/v4/token | POST /auth/token | |
| DID Registry | /did-registry/v5/identifiers | /did-registry/identifiers |
/did-registry/v5/identifiers/{did} | /did-registry/identifiers/{did} | |
/did-registry/v5/abi | /did-registry/abi | |
| (none) | /did-registry/details (new) | |
| Contracts Registry | /trusted-contracts-registry/v1/contracts | /contracts-registry/contracts |
/trusted-contracts-registry/v1/contracts/{address} | /contracts-registry/contracts/{address} | |
/trusted-contracts-registry/v1/templates | /contracts-registry/templates | |
/trusted-contracts-registry/v1/templates/{id} | /contracts-registry/templates/{id} | |
/trusted-contracts-registry/v1/abi | /contracts-registry/abi | |
| Issuers Registry | /trusted-issuers-registry/v5/issuers | /issuers-registry/issuers |
/trusted-issuers-registry/v5/issuers/{did} | /issuers-registry/issuers/{did} | |
/trusted-issuers-registry/v5/issuers/{did}/attributes[/{id}[/revisions[/{revId}]]] | /issuers-registry/issuers/{did}/attributes/… | |
/trusted-issuers-registry/v5/issuers/{did}/proxies[/{proxyId}[/{path}]] | /issuers-registry/issuers/{did}/proxies/… | |
/trusted-issuers-registry/v5/abi | /issuers-registry/abi | |
| (none) | /issuers-registry/details (new) | |
| Policies Registry | /trusted-policies-registry/v3/policies[/{policyName}] | /policies-registry/policies[/{policyName}] |
/trusted-policies-registry/v3/subjects[/{subject}[/policies[/{policyName}]]] | /policies-registry/subjects/… | |
/trusted-policies-registry/v3/abi | /policies-registry/abi | |
| Schemas Registry | /trusted-schemas-registry/v3/schemas[/{schemaId}[/revisions[/{revId}[/metadata[/{metaId}]]]]] | /schemas-registry/schemas/… |
/trusted-schemas-registry/v3/abi | /schemas-registry/abi | |
| (none) | /schemas-registry/details (new) | |
| Timestamp | /timestamp/v4/hash-algorithms[/{id}] | /timestamp/hash-algorithms[/{id}] |
/timestamp/v4/timestamps[/{timestampId}] | /timestamp/timestamps[/{timestampId}] | |
/timestamp/v4/records[/{recordId}[/versions[/{versionId}]]] | /timestamp/records/… | |
/timestamp/v4/abi | /timestamp/abi | |
| (none) | /timestamp/details (new) | |
| Track & Trace | /track-and-trace/v1/documents[/{id}[/events[/{eventId}]]] | /track-and-trace/documents/… |
/track-and-trace/v1/documents/{id}/accesses | /track-and-trace/documents/{id}/accesses | |
GET & HEAD /track-and-trace/v1/accesses | /track-and-trace/accesses | |
/track-and-trace/v1/abi | /track-and-trace/abi | |
| (none) | /track-and-trace/details (new) |
6. Removed / discontinued
| Removed | Replacement |
|---|---|
POST /did-registry/v5/jsonrpc | POST /v1/ledger (eth_sendRawTransaction) |
POST /timestamp/v4/jsonrpc | POST /v1/ledger |
POST /track-and-trace/v1/jsonrpc | POST /v1/ledger |
POST /trusted-issuers-registry/v5/jsonrpc | POST /v1/ledger |
POST /trusted-policies-registry/v3/jsonrpc | POST /v1/ledger |
POST /trusted-schemas-registry/v3/jsonrpc | POST /v1/ledger |
POST /ledger/v4/blockchains/besu | POST /v1/ledger |
POST /did-registry/v5/identifiers/{did}/actions (checkController) | No REST replacement read the controller from the DID document or via eth_call to the DID Registry contract |
GET /trusted-policies-registry/v3/users | No REST replacement read user attributes on-chain via eth_call |
GET /trusted-policies-registry/v3/users/{user} | as above |
GET /auth/presentation-definitions (OpenID4VP draft 14) | GET /auth/dcql-queries |
7. Migration checklist
- Repoint clients at
<base>/v1delete per-service version segments (/v5,/v4,/v3,/v1). - Rename paths:
/authorisation/v4/*to/auth/*,/trusted-*-registry/*to/*-registry/*. - Change every requested scope from
_to:(didr_writetodid-registry:write, …). - Replace the per-service
jsonrpcbuild, sign,sendSignedTransactionflow with: build calldata fromGET /v1/<registry>/abi, sign locally,POST /v1/ledgereth_sendRawTransaction. - Move Besu read calls from
/ledger/v4/blockchains/besuto/v1/ledger. - For DID writes, keep the same method names (
updateBaseDocument,addVerificationMethod,addController, …) just move them onto/v1/ledgereth_sendRawTransaction. - For Timestamp hash-timestamping and Track & Trace
createDocument, add thecommitTimestampHashes/commitCreateDocumenttransaction (same sender, ≥ 5 blocks earlier) and pass thesalton the reveal. - Handle
405/406and the400Invalid Contract Address response and parsePOST /auth/tokenerrors as OAuth 2.0 error JSON, notproblem+json. - For DID onboarding, the did-registry:invite token is locked to one key. It carries an RFC 7800 claim
"cnf": { "jkt": "<RFC 7638 thumbprint>" }of the key the holder proved possession of in the Verifiable Presentation. The insertDidDocument call you send through POST /v1/ledger must register a publicKey whose RFC 7638 thumbprint matches cnf.jkt. It cannot register any other key. - The DID document
Acceptheader still works onGET /v1/did-registry/identifiers/{did}, no client change needed. An exactAccept: application/did+jsonreturns the @context-free form. Anything else acceptable,Accept: */*included, returnsapplication/did+ld+json. An unsatisfiable Accept gets 406. - Drop
checkControllerand the Policies Registry /users reads. Neither has a v1 REST replacement. ForcheckController(formerlyPOST /did-registry/v5/identifiers/{did}/actions), read the controller field fromGET /v1/did-registry/identifiers/{did}, or filter the collection withGET /v1/did-registry/identifiers?controller=<did>and check whether the DID appears, or call the DID Registry contract witheth_callthroughPOST /v1/ledger. ForGET /trusted-policies-registry/v3/usersandGET /trusted-policies-registry/v3/users/{user}, read the same attributes on-chain by calling the Policies Registry contract witheth_callthroughPOST /v1/ledger, using the address and ABI fromGET /v1/policies-registry/abi.eth_callreads need no authentication. Note thatGET /v1/policies-registry/subjectsandGET /v1/policies-registry/subjects/{subject}still exist and cover policy-to-subject lookups. - Optional: adopt combined-scope tokens and
:<0x-address>instance targeting (registered beacon proxies only) useGET /v1/<registry>/detailsto check inter-registry links. - Migrate to the OpenID4VP 1.0 (final) grant. Draft 14 is removed. Replace
GET /auth/presentation-definitionswithGET /auth/dcql-queries, submitvp_tokenas a JSON object keyed by credential-query identifier, omitpresentation_submission, and for the seven combinable scopes present a self-issuedSelfAttestationcredential in place of an empty presentation. - Handle Problem Details: match on
type(urn:ebsi:problems:*), read theerrorsarray, and don't parsedetail. - Discovery: stop reading
authorization_endpointandpresentation_definition_endpoint, and verify ID token signatures (ES256).