API Reference
Overview
Section titled “Overview”This page lists every public and admin endpoint exposed by TCS. Internal endpoints used for inter-service communication are excluded. Health check endpoints (GET /health) are available on all services but omitted from this reference.
For authentication details on each endpoint, see the Authentication reference.
OpenAPI specification
Section titled “OpenAPI specification”If you need a machine-readable spec for SDK generation or automated tooling, contact the TCS team.
Service Endpoints
Section titled “Service Endpoints”Base URL: https://auth.turingspace.co
The Authorization Server handles OAuth 2.0 token issuance, DPoP validation, and the authorization code flow. It publishes discovery metadata per RFC 8414.
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /.well-known/oauth-authorization-server | RFC 8414 discovery document. Returns issuer, token_endpoint, pushed_authorization_request_endpoint, and dpop_signing_alg_values_supported among other fields. | Public |
| POST | /v1/token | Exchange pre-authorized code or authorization code for access token. | Public |
| POST | /v1/par | Create a Pushed Authorization Request session (RFC 9126). | Public |
The authorization endpoint (GET /v1/authorize), the login form submit (POST /v1/authorize/submit), and the logout endpoint (GET /end_session) are browser-driven and not part of the integration API surface. They are listed in the discovery document for spec compliance.
GET /.well-known/oauth-authorization-server — RFC 8414 AS metadata discovery
Returns the Authorization Server metadata document. Wallets and clients fetch this at startup to discover endpoint URLs, supported grant types, DPoP algorithms, and client attestation requirements.
Auth: Public · Content-Type: N/A (GET)
Response 200
{ "issuer": "https://auth.turingspace.co", "authorization_endpoint": "https://auth.turingspace.co/v1/authorize", "token_endpoint": "https://auth.turingspace.co/v1/token", "pushed_authorization_request_endpoint": "https://auth.turingspace.co/v1/par", "end_session_endpoint": "https://auth.turingspace.co/end_session", "response_types_supported": ["code"], "grant_types_supported": [ "authorization_code", "urn:ietf:params:oauth:grant-type:pre-authorized_code" ], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["attest_jwt_client_auth"], "client_attestation_signing_alg_values_supported": ["ES256", "EdDSA"], "client_attestation_pop_signing_alg_values_supported": ["ES256", "EdDSA"], "dpop_signing_alg_values_supported": ["ES256", "EdDSA"], "authorization_details_types_supported": ["openid_credential"], "authorization_response_iss_parameter_supported": true, "id_token_signing_alg_values_supported": ["ES256"]}Errors — None expected under normal operation. Returns 200 always.
See also: Authentication reference
POST /v1/par — Create PAR session (RFC 9126)
Pushes an authorization request to the AS and receives a request_uri handle. The wallet then redirects the user to the AS authorization endpoint with this handle. This is the first step of the authorization-code issuance flow; the pre-authorized_code flow bypasses PAR entirely.
Auth: Public · Content-Type: application/x-www-form-urlencoded
Client attestation required by default. The AS ships with
CLIENT_ATTESTATION_REQUIRED_AUTH_CODE=true. IncludeOAuth-Client-Attestation+OAuth-Client-Attestation-PoPheaders or the request will be rejected with401. See Authentication for details.
Request fields
response_type(required, string) — Must becode.client_id(required, string) — Client identifier, e.g.test-wallet.redirect_uri(required, URL) — Auth code delivery URL, e.g.https://wallet.example.com/callback.code_challenge(required, string) — PKCE challenge:base64url(SHA-256(code_verifier)), e.g.E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM.code_challenge_method(required, string) — Must beS256.scope(optional, string) — Credential configuration ID per HAIP §4.3, e.g.TuringCerts_Standard_Credential_v2_sd_jwt.issuer_state(optional, UUID) — Carried from credential offer’sauthorization_code.issuer_state.authorization_details(optional, JSON array) — RFC 9396 / OID4VCI §5.1.1, e.g.[{"type":"openid_credential","credential_configuration_id":"UniversityDegree"}].state(optional, string ≤ 4096 chars) — Wallet CSRF token.request_uri— Forbidden in PAR body (RFC 9126 §2.1); rejected withinvalid_request.
Response 201
{ "request_uri": "urn:ietf:params:oauth:request-uri:bwc4JZ-ESnd0kX1aIAr5pg", "expires_in": 60}Errors — { "error": "...", "error_description": "..." } (RFC 6749 §5.2)
400 invalid_request— Missing required field,request_uripresent in body, orissuer_stateis not a valid UUID.
POST /v1/token — Exchange code for access token
Issues an access token in exchange for a pre-authorized code (pre-authorized_code grant) or an authorization code (authorization_code grant). The token response carries a Bearer or DPoP-bound access token used at the Credential Endpoint.
Auth: Public · Content-Type: application/x-www-form-urlencoded
Request fields — pre-authorized_code grant
grant_type(required, string) —urn:ietf:params:oauth:grant-type:pre-authorized_code.pre-authorized_code(required, string) — Code from the credential offer, e.g.SplxlOBeZQQYbYS6WxSbIA.tx_code(conditional, string) — Transaction code (user PIN) iftx_codeis present in the offer, e.g.123456.
Request fields — authorization_code grant
grant_type(required, string) —authorization_code.code(required, string) — Authorization code from the redirect callback.code_verifier(required, string) — PKCE verifier, e.g.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk.redirect_uri(required, string) — Must match the URI in the PAR session.client_id(required, string) — Client identifier.
Response 200
{ "access_token": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "DPoP", "expires_in": 3600}token_type is DPoP when the request included a DPoP header, otherwise Bearer.
Errors — { "error": "...", "error_description": "..." } (RFC 6749 §5.2)
400 invalid_grant— Expired or already-used pre-authorized code.400 unsupported_grant_type— Unknowngrant_typevalue.400 use_dpop_nonce— AS requires a fresh DPoP nonce; retry withDPoP-Nonceheader.400 invalid_dpop_proof— Malformed or expired DPoP proof JWT.
See also: Wallet protocol — token exchange
Base URL: https://issuer.turingspace.co
The Credential Issuer manages credential offers, serves issuer metadata for wallet discovery, and issues SD-JWT VCs upon token-authenticated requests.
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/offers | Create a credential offer. See Step 1 · Create a credential offer. | API Key |
| GET | /v1/offers/:offerId | Retrieve credential offer details in OID4VCI format. Called by wallets after scanning the offer URI. | Public |
| POST | /v1/nonce | Obtain a fresh c_nonce for credential request proofs (OID4VCI Section 7). See Step 3 — Fetch a fresh c_nonce. | Public |
| POST | /credential | Issue an SD-JWT VC. Supports Bearer and DPoP tokens. See Step 5 — Request the credential. | Bearer / DPoP |
| GET | /.well-known/openid-credential-issuer/:tenant | OID4VCI Section 11 issuer metadata. Returns credential_endpoint, credential_configurations_supported, authorization_servers, and nonce_endpoint. | Public |
| GET | /:tenant | Tenant landing page with issuer information and metadata links. | Public |
POST /v1/offers — Create a credential offer
Creates a credential offer that encodes what credential type is available and how the wallet should obtain it (pre-authorized_code or authorization_code flow). Returns a deep_link (openid-credential-offer://) to present to the holder as a QR code or direct link.
Auth: API Key · Content-Type: application/json
Request fields
credential(required, object) — Credential specification:config_id(required, string) — TCS shorthand for the OID4VCIcredential_configuration_iddeclared in issuer metadata, e.g.UniversityDegree.claims(required, object) — Claim key-value pairs to include, e.g.{"degree": "Bachelor of Science", "major": "Computer Science"}.
flow(required, string) —pre-authorizedorauthorization-code.issuer_did(optional, string) — DID of the credential issuer for DID-based signing, e.g.did:iota:issuer-ntu-001. When provided,issuer_private_keyis also required.issuer_private_key(optional, string) — Ed25519: base64url-encoded seed; ES256: PKCS#8 PEM. Required whenissuer_didis provided.holder_did(optional, string) — DID of the holder for credential binding, e.g.did:iota:holder-xyz.expires_in(optional, integer, 60–86400) — Offer TTL in seconds (default: 3600).require_transaction_code(optional, boolean) — Whether atx_codePIN is required (default:false). Settingtrueis currently rejected at the public endpoint with400; the auth-server validation side is ready, but issuer-side enablement (tx_code generation + user notification) is coordinated with the TCS team during onboarding. Leavefalseor omit for self-serve issuance today.
Signing mode: When issuer_did + issuer_private_key are both provided, uses DID-based signing. When both are omitted, uses X.509 server-side signing. Providing one without the other returns a 400 validation error.
Response 201
{ "offer_id": "550e8400-e29b-41d4-a716-446655440000", "credential_offer_uri": "https://issuer.turingspace.co/v1/offers/550e8400-e29b-41d4-a716-446655440000", "deep_link": "openid-credential-offer://?credential_offer_uri=https%3A%2F%2Fissuer.turingspace.co%2Fv1%2Foffers%2F550e8400-e29b-41d4-a716-446655440000", "expires_at": "2024-12-31T12:00:00.000Z"}Errors
400 Bad Request— Missing required field, invalid DID format, or mismatchedissuer_did/issuer_private_key.401 Unauthorized— Missing or invalid API Key.
See also: Issuer — issue a credential
GET /v1/offers/:offerId — Retrieve credential offer (wallet endpoint)
Returns the OID4VCI CredentialOffer object for the given offer ID. Wallets call this URL after resolving the credential_offer_uri from the deep link or QR code.
Auth: Public · Content-Type: N/A (GET)
Path parameters
offerId(required, UUID) — Offer identifier fromPOST /v1/offers.
Response 200
{ "credential_issuer": "https://issuer.turingspace.co/turing", "credential_configuration_ids": ["UniversityDegree"], "grants": { "urn:ietf:params:oauth:grant-type:pre-authorized_code": { "pre-authorized_code": "oaKazRN8I0IbtZ0C7JuMn5", "tx_code": { "input_mode": "numeric", "length": 6, "description": "Please enter the transaction code sent to your email" } } }, "expires_at": "2024-12-31T12:00:00.000Z"}The tx_code object is present when the offer was created with require_transaction_code: true — the wallet prompts the user for the code, then sends it to POST /v1/token as the tx_code field. The auth server validates the hash. Enabling require_transaction_code: true on the offer endpoint is coordinated with the TCS team during onboarding; the public POST /v1/offers currently rejects true until coordination is done. grants.authorization_code replaces pre-authorized_code for authorization-code flow offers, with an optional issuer_state field.
Errors
400 Bad Request— Invalid UUID format inofferIdpath parameter.404 Not Found— Offer not found.410 Gone— Offer has expired.
See also: Wallet protocol — parse the offer
POST /v1/nonce — Obtain a fresh c_nonce
Returns a c_nonce challenge that the wallet must include in the proof-of-possession JWT sent with the credential request. Required by OID4VCI §7 (Nonce Endpoint) for key binding.
Auth: Public · Content-Type: application/json (empty body accepted)
Response 200
{ "c_nonce": "tZignsnFbp"}Errors — None expected under normal operation.
See also: Wallet protocol — fetch a fresh c_nonce
POST /credential — Issue an SD-JWT VC
Issues an SD-JWT VC (dc+sd-jwt format) to the authenticated wallet. The wallet presents an access token (Bearer or DPoP-bound) obtained from the token endpoint, and optionally a proof of possession over a holder key.
Auth: Bearer or DPoP access token (via Authorization header) · Content-Type: application/json
Request fields
credential_configuration_id(conditional, string) — ID from issuer metadata, e.g.UniversityDegree. Required unlesscredential_identifieris used.credential_identifier(conditional, string) — OID4VCI §7.2 alternative tocredential_configuration_id; mutually exclusive.proofs(optional, object) — Proof of possession for key binding:jwt(required withinproofs, array of strings) — One or more JWT proofs containing thec_nonceand the holder’s DID or key.
is_on_chain(optional, boolean, defaultfalse) — Publish the credential on the IOTA blockchain.
Response 200
{ "credentials": [ { "credential": "eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCJ9.eyJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6e319fQ~...", "reference": { "chain": "iota", "object_id": "0x1234567890abcdef..." } } ]}reference is present only when is_on_chain=true.
Errors
400 invalid_credential_request— Bothcredential_configuration_idandcredential_identifierprovided, orcredential_identifier-only request (not yet supported).401 Unauthorized— Missing or invalid access token.400 invalid_proof— Proof JWT malformed, nonce mismatch, or signature invalid.
See also: Wallet protocol — request the credential
GET /.well-known/openid-credential-issuer/:tenant — OID4VCI issuer metadata
Returns the OID4VCI Credential Issuer Metadata document for the named tenant. Wallets fetch this automatically by appending /.well-known/openid-credential-issuer/{tenant} to the credential_issuer URL from the offer.
Auth: Public · Content-Type: N/A (GET)
Path parameters
tenant(required, string) — Tenant identifier, e.g.turing.
Response 200
{ "credential_issuer": "https://issuer.turingspace.co/turing", "authorization_servers": ["https://auth.turingspace.co"], "credential_endpoint": "https://issuer.turingspace.co/credential", "nonce_endpoint": "https://issuer.turingspace.co/v1/nonce", "credential_configurations_supported": { "UniversityDegree": { "format": "dc+sd-jwt", "scope": "TuringCerts_Standard_Credential_v2_sd_jwt", "vct": "https://schema-registry.turingspace.co/UniversityDegree/v1.0", "credential_signing_alg_values_supported": ["EdDSA"], "cryptographic_binding_methods_supported": ["did"], "proof_types_supported": { "jwt": { "proof_signing_alg_values_supported": ["ES256", "EdDSA"] } } } }}Errors
404 Not Found— Tenant not found.
See also: Discover a credential type
GET /:tenant — Tenant landing page
Returns a human-readable HTML page for the tenant credential issuer. Useful for developer verification and debugging — confirms the tenant is configured and provides a link to the issuer metadata endpoint.
Auth: Public · Content-Type: N/A (GET, returns HTML)
Path parameters
tenant(required, string) — Tenant identifier, e.g.turing.
Response 200 — HTML page with issuer identifier, metadata endpoint URL, and debugging links.
Errors
404 Not Found— Tenant not found.
See also: the GET /.well-known/openid-credential-issuer/:tenant block above for issuer metadata.
Base URL: https://verifier.turingspace.co
The Verifier Service implements OID4VP for credential verification. Verifier clients create authorization requests, wallets fetch signed request objects and submit VP Tokens, and clients poll for verification results.
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/verifier/authorization-request | Create an OID4VP authorization request. See Step 1 · Create an authorisation request. | API Key |
| GET | /v1/verifier/request/:requestUriId | Fetch signed JAR request object (wallet endpoint). See Step 2 — Parse the request URI. | Public |
| POST | /v1/verifier/presentation | Submit VP Token for verification via direct_post. See Step 6 — Submit the response. | Public |
| GET | /v1/verifier/result/:sessionId | Query verification session status and results. See Step 3 · Poll the result. | API Key |
POST /v1/verifier/authorization-request — Create an OID4VP authorization request
Creates a verification session and returns a signed JAR request_uri that the verifier client encodes as a QR code or deep link (openid4vp://). The wallet scans this URI, fetches the signed request object, and submits a VP Token.
Auth: API Key · Content-Type: application/json
Request fields
dcql_query(required, object) — DCQL query specifying the required credentials:credentials(required, array) — One or more credential query objects:id(required, string) — Query identifier, e.g.identity_credential.format(required, string) — Credential format, e.g.dc+sd-jwt.meta(required, object) — Format-specific metadata:vct_values(optional, array of strings) — Allowed VCT URIs fordc+sd-jwtformat.
claims(optional, array) — Requested claims withpath(JSON pointer array) and optionalvalues.multiple(optional, boolean) — Allow multiple matching credentials (default:false).require_cryptographic_holder_binding(optional, boolean, default:true) — Require Key Binding JWT.trusted_authorities(optional, array) — Trusted issuer list:[{"type": "did", "values": ["did:..."]}].
verifier_private_key(required, string) — Base64url Ed25519 private key (32 bytes) for JAR signing.verifier_client_id(required, string) — Verifier DID or URL, e.g.did:example:verifier123.response_mode(optional, string) —direct_post(default) ordirect_post.jwt(encrypted).client_metadata(optional, object) — Display info for wallet UI:client_name,logo_uri,policy_uri,tos_uri.redirect_uri(optional, URL) — Verifier callback for same-device flow; receives?session_id=...after submission.expires_in(optional, integer, 60–3600) — Session TTL in seconds (default: 300).
Response 201
{ "session_id": "sess_1a2b3c4d5e", "request_uri": "https://verifier.turingspace.co/v1/verifier/request/req_xyz789", "authorization_request": "openid4vp://?client_id=did%3Aexample%3Averifier123&request_uri=https%3A%2F%2Fverifier.turingspace.co%2Fv1%2Fverifier%2Frequest%2Freq_xyz789", "expires_at": "2025-12-31T10:15:00Z"}Errors
400 Bad Request— DCQL query missing or invalid;credentialsarray empty.401 Unauthorized— Missing or invalid API Key.
See also: Verifier — verify a credential
GET /v1/verifier/request/:requestUriId — Fetch signed JAR request object (wallet endpoint)
Returns the signed JWT Authorization Request (JAR) for the given request URI ID. Wallets call this URL after resolving the request_uri from the openid4vp:// deep link. The response is a compact JWT signed by the verifier’s private key.
Auth: Public · Content-Type: N/A (GET)
Path parameters
requestUriId(required, string) — Request URI ID fromauthorization_requestresponse.
Response 200 — JWT string (Content-Type: application/oauth-authz-req+jwt). Decoded payload contains:
{ "iss": "did:example:verifier123", "aud": "https://self-issued.me/v2", "client_id": "https://verifier.turingspace.co", "response_type": "vp_token", "response_mode": "direct_post", "response_uri": "https://verifier.turingspace.co/v1/verifier/presentation", "dcql_query": { "credentials": [ { "id": "identity_credential", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://schema-registry.turingspace.co/IdentityCredential/v1"] }, "claims": [{ "path": ["family_name"] }] } ] }, "nonce": "550e8400-e29b-41d4-a716-446655440000", "state": "660e8400-e29b-41d4-a716-446655440001", "iat": 1735552800, "exp": 1735553100}Errors
404 Not Found— Request URI ID not found or expired.
See also: Wallet protocol — parse the request URI
POST /v1/verifier/presentation — Submit VP Token (wallet endpoint)
Receives the wallet’s VP Token submission via direct_post (or encrypted direct_post.jwt). Verifies the SD-JWT VC presentation against the DCQL query and records the result in the session.
Auth: Public · Content-Type: application/x-www-form-urlencoded
Request fields
vp_token(conditional, string) — VP Token JSON object mapping credential query ID to array of SD-JWT presentations, e.g.{"identity_credential":["eyJhbGciOiJFUzI1NiIs..."]}. Required fordirect_postmode.state(required, string) — Session state from the JAR request object.response(conditional, string) — Encrypted JWE containingvp_token. Required fordirect_post.jwtmode.presentation_submission(optional, string) — DIF Presentation Exchange descriptor map JSON.verification_mode(optional, string) —fail_fast(stop at first failure) orrun_all(run all checks). Default:fail_fast.
Response 200
{ "redirect_uri": "https://verifier-client.example.com/callback?session_id=660e8400-e29b-41d4-a716-446655440001"}redirect_uri is present only when the verifier provided a redirect_uri at session creation. The session_id query parameter matches the session UUID from POST /v1/verifier/authorization-request.
Errors
400 Bad Request— Missingstate,vp_token/responseboth absent, or session not found/expired (invalid_request).
See also: Wallet protocol — submit the response
GET /v1/verifier/result/:sessionId — Query verification result
Polls the verification session status. While the wallet has not yet submitted a VP Token the status is pending. After submission and processing it transitions to completed or failed. Expired sessions return expired.
Auth: API Key · Content-Type: N/A (GET)
Path parameters
sessionId(required, string) — Session ID fromPOST /v1/verifier/authorization-request.
Response 200
{ "session_id": "sess_1a2b3c4d5e", "status": "completed", "created_at": "2025-12-31T10:00:00Z", "expires_at": "2025-12-31T10:05:00Z", "verified_at": "2025-12-31T10:01:30Z", "verification_mode": "fail_fast", "check_results": [ { "check": "credential_format", "status": "success", "reason_code": "credential_format_valid", "message": "Credential format is valid" } ], "presentation_metadata": { "claims": { "family_name": "Doe", "given_name": "Jane" }, "credential_metadata": [ { "issuer": "did:iota:0x1234...", "credential_type": ["VerifiableCredential", "IdentityCredential"], "issued_at": "2025-01-01T00:00:00Z", "expires_at": "2026-01-01T00:00:00Z" } ] }}status values: pending | in_progress | completed | failed | expired. presentation_metadata is only present when status=completed. error object (with code and message) is only present when status=expired.
Errors
401 Unauthorized— Missing or invalid API Key.404 Not Found— Session not found.
See also: Verifier — poll the result
Base URL: https://holder.turingspace.co
The Holder Service provides a custodial wallet with user account management, DID operations, credential storage, and OID4VCI/OID4VP client functionality.
User Management
Section titled “User Management”| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/user/register | Register a new holder account. See Register an end user. | Public |
| POST | /v1/user/login | Authenticate and receive a JWT access token. See Log the user in. | Public |
POST /v1/user/register — Register a new holder account
Creates a new custodial wallet account. Returns a UUID that the user must save — it is required for login.
Auth: Public · Content-Type: application/json
Request fields
password(required, string, min 8 chars) — Account password, e.g.SecureP@ss123.name(optional, string, max 100 chars) — Display name, e.g.John Doe.
Response 201
{ "uuid": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe", "created_at": "2026-01-28T10:00:00.000Z"}Errors
400 Bad Request— Password too short.
See also: Holder — manage wallets
POST /v1/user/login — Authenticate and receive a JWT
Authenticates with UUID and password and returns a Bearer JWT for subsequent requests.
Auth: Public · Content-Type: application/json
Request fields
uuid(required, UUID v4) — User UUID from registration, e.g.550e8400-e29b-41d4-a716-446655440000.password(required, string) — Account password.
Response 200
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 86400, "user": { "uuid": "550e8400-e29b-41d4-a716-446655440000", "name": "John Doe" }}Errors
400 Bad Request— Invalid UUID format.401 Unauthorized— Wrong UUID or password.
See also: Holder — log the user in
Verifiable Credentials
Section titled “Verifiable Credentials”| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /v1/vc | List all credentials for the authenticated holder. See List a user’s credentials. | Bearer JWT |
| GET | /v1/vc/:id | Get credential details including claims and raw SD-JWT. See Get one credential. | Bearer JWT |
| DELETE | /v1/vc/:id | Soft-delete a credential. See Delete a credential. | Bearer JWT |
GET /v1/vc — List credentials
Returns all credentials stored for the authenticated holder.
Auth: Bearer JWT · Content-Type: N/A (GET)
Response 200
{ "credentials": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "vct": "https://credentials.example.com/identity", "issuer_did": "did:iota:testnet:0x1234567890abcdef...", "holder_did": "did:iota:testnet:0x5678901234abcdef...", "issued_at": "2026-02-03T10:00:00.000Z", "expires_at": "2027-02-03T10:00:00.000Z", "status": "active", "created_at": "2026-02-03T10:00:00.000Z" } ]}Errors
401 Unauthorized— Missing or invalid JWT.
See also: Holder — list credentials
GET /v1/vc/:id — Get credential detail
Returns full credential details including the decoded claims object and the raw SD-JWT string.
Auth: Bearer JWT · Content-Type: N/A (GET)
Path parameters
id(required, UUID) — Credential ID.
Response 200
{ "id": "550e8400-e29b-41d4-a716-446655440000", "vct": "https://credentials.example.com/identity", "issuer_did": "did:iota:testnet:0x1234...", "holder_did": "did:iota:testnet:0x5678...", "issued_at": "2026-02-03T10:00:00.000Z", "expires_at": "2027-02-03T10:00:00.000Z", "status": "active", "created_at": "2026-02-03T10:00:00.000Z", "claims": { "name": "John Doe", "degree": "Bachelor of Science" }, "raw_credential": "eyJ...", "reference": { "block_chain": "IOTA", "object_id": "0x1234567890abcdef..." }}reference is only present for on-chain credentials.
Errors
401 Unauthorized— Missing or invalid JWT.404 Not Found— Credential not found or belongs to another holder.
See also: Holder — get one credential
DELETE /v1/vc/:id — Delete a credential
Soft-deletes a credential from the holder’s wallet. The raw SD-JWT is not destroyed, but the credential is removed from active listings.
Auth: Bearer JWT · Content-Type: N/A
Path parameters
id(required, UUID) — Credential ID.
Response 204 No Content — No body.
Errors
401 Unauthorized— Missing or invalid JWT.404 Not Found— Credential not found or belongs to another holder.
See also: Holder — delete a credential
DID Management
Section titled “DID Management”| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/did | Create a new IOTA DID (private key returned once). See Create a DID for the user. | Bearer JWT |
| POST | /v1/did/import | Import an existing IOTA DID with ownership verification. See List or delete a user’s DIDs. | Bearer JWT |
| GET | /v1/did | List all DIDs for the authenticated holder. See List or delete a user’s DIDs. | Bearer JWT |
| DELETE | /v1/did/:did | Delete a DID (URL-encoded DID URL in path). See List or delete a user’s DIDs. | Bearer JWT |
POST /v1/did — Create a new DID
Creates a new DID on the IOTA network and returns the DID URL and private key. The private key is returned only once — the holder must store it securely.
Auth: Bearer JWT · Content-Type: application/json
Request fields
method(required, string) — DID method. CurrentlyIOTA.
Response 201
{ "did": "did:iota:testnet:0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b", "private_key": "nwKZ3iFzL7pZJK5pPJ-8k9vXcL2vL7pZJK5pPJ-8k9vXcL2vL7pZJK5pPJ-8k9vXcL2vL7pZJK"}The private_key is returned only once. Save it immediately — there is no recovery path.
Errors
401 Unauthorized— Missing or invalid JWT.400 Bad Request— Unsupportedmethodvalue.
See also: Holder — create a DID
POST /v1/did/import — Import an existing DID
Associates an existing IOTA DID with the authenticated holder by verifying ownership via the private key.
Auth: Bearer JWT · Content-Type: application/json
Request fields
did(required, string) — IOTA DID URL, must start withdid:iota:, e.g.did:iota:testnet:0x7a8b9c....private_key(required, string) — Base64url encoded Ed25519 private key for ownership verification.
Response 201
{ "did": "did:iota:testnet:0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b", "method": "IOTA", "status": "active"}Errors
400 Bad Request— DID does not start withdid:iota:, or private key verification failed.401 Unauthorized— Missing or invalid JWT.
See also: Holder — manage DIDs
GET /v1/did — List DIDs
Returns all DIDs registered under the authenticated holder.
Auth: Bearer JWT · Content-Type: N/A (GET)
Response 200
{ "dids": [ { "did": "did:iota:testnet:0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b", "method": "IOTA", "status": "active", "created_at": "2026-01-30T10:30:00.000Z" } ]}Errors
401 Unauthorized— Missing or invalid JWT.
See also: Holder — manage DIDs
DELETE /v1/did/:did — Delete a DID
Removes a DID from the holder’s wallet. The DID URL must be URL-encoded in the path parameter.
Auth: Bearer JWT · Content-Type: N/A
Path parameters
did(required, string) — URL-encoded DID URL, e.g.did%3Aiota%3Atestnet%3A0x7a8b....
Response 204 No Content — No body.
Errors
401 Unauthorized— Missing or invalid JWT.404 Not Found— DID not found or belongs to another holder.
See also: Holder — manage DIDs
OID4VCI Client
Section titled “OID4VCI Client”| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/oid4vci/offer-details | Parse a credential offer URI and retrieve offer details. See Optional: preview the offer. | Bearer JWT |
| POST | /v1/oid4vci/receive | Execute the OID4VCI flow to receive a credential. See Accept the offer. | Bearer JWT |
POST /v1/oid4vci/offer-details — Preview a credential offer
Parses a credential_offer_uri deep link, fetches the OID4VCI offer and issuer metadata, and returns structured offer details for the holder to review before accepting.
Auth: Bearer JWT · Content-Type: application/json
Request fields
credential_offer_uri(required, string) —openid-credential-offer://deep link, e.g.openid-credential-offer://?credential_offer_uri=https://issuer.example.com/v1/offers/abc123.
Response 200
{ "issuer": "https://issuer.turingspace.co/turing", "issuer_name": "Turing Space", "issuer_logo_uri": "https://issuer.turingspace.co/logo.png", "credential_configurations": [ { "id": "UniversityDegree", "format": "dc+sd-jwt", "vct": "https://schema-registry.turingspace.co/UniversityDegree/v1.0", "display": { "name": "University Degree", "locale": "en" } } ], "pre_authorized_code_grant": { "pre_authorized_code": "oaKazRN8I0IbtZ0C7JuMn5", "user_pin_required": false }, "expires_at": "2024-12-31T12:00:00.000Z"}Errors
400 Bad Request— Invalid or expired offer URI.401 Unauthorized— Missing or invalid JWT.
See also: Holder — preview the offer
POST /v1/oid4vci/receive — Execute OID4VCI to receive a credential
Runs the complete OID4VCI issuance flow on behalf of the holder: resolves the offer, exchanges the pre-authorized code for a token, fetches a c_nonce, generates a proof of possession, requests the credential, and stores it.
Auth: Bearer JWT · Content-Type: application/json
Request fields
credential_offer_uri(required, string) —openid-credential-offer://deep link.holder_did(required, string) — Holder DID URL for key binding, e.g.did:iota:testnet:0x5678....private_key(required, string) — Base64url encoded holder private key. Ed25519 fordid:iota; ES256 P-256 rawdfordid:key.credential_configuration_id(optional, string) — Selects a specific config when the offer contains multiple.user_pin(optional, string) — Transaction code if required by the issuer.notarize_on_iota(optional, boolean, default:true) — Publish the JWT payload on IOTA for on-chain notarization.
Response 201
{ "credential_id": "550e8400-e29b-41d4-a716-446655440000", "vct": "https://schema-registry.turingspace.co/UniversityDegree/v1.0", "issuer_did": "did:iota:issuer-ntu-001", "holder_did": "did:iota:testnet:0x5678...", "raw_credential": "eyJhbGciOiJFZERTQSIsInR5cCI6InZjK3NkLWp3dCJ9...", "issued_at": "2026-02-03T10:00:00.000Z", "expires_at": "2027-02-03T10:00:00.000Z", "status": "active"}Errors
400 Bad Request— Invalid offer, expired pre-authorized code, or proof verification failure.401 Unauthorized— Missing or invalid JWT.
See also: Holder — accept the offer
OID4VP Client
Section titled “OID4VP Client”| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/oid4vp/request-details | Parse an OID4VP authorization request URI, fetch and verify the request object, match credentials. See Parse the verifier’s request. | Bearer JWT |
| POST | /v1/oid4vp/presentation | Build and submit a VP Token with selective disclosure and Key Binding JWT. See Submit the presentation. | Bearer JWT |
POST /v1/oid4vp/request-details — Parse an OID4VP authorization request
Resolves an openid4vp:// URI, fetches and verifies the signed JAR request object, and matches the DCQL credential query against the holder’s stored credentials. Returns the parsed request details and a list of matching credentials.
Auth: Bearer JWT · Content-Type: application/json
Request fields
authorization_request_uri(required, string) —openid4vp://deep link, e.g.openid4vp://?client_id=did:iota:testnet:0x1234...&request_uri=https://verifier.example.com/v1/verifier/request/abc123.
Response 200
{ "client_id": "did:example:verifier123", "client_metadata": { "client_name": "Example Service", "logo_uri": "https://example.com/logo.png" }, "dcql_query": { "credentials": [ { "id": "identity_credential", "format": "dc+sd-jwt", "meta": { "vct_values": ["https://schema-registry.turingspace.co/IdentityCredential/v1"] }, "claims": [{ "path": ["family_name"] }] } ] }, "matching_credentials": [ { "credential_id": "550e8400-e29b-41d4-a716-446655440000", "query_id": "identity_credential", "vct": "https://schema-registry.turingspace.co/IdentityCredential/v1", "claims": { "family_name": "Doe", "given_name": "Jane" } } ], "nonce": "550e8400-e29b-41d4-a716-446655440000", "state": "660e8400-e29b-41d4-a716-446655440001", "response_mode": "direct_post", "response_uri": "https://verifier.turingspace.co/v1/verifier/presentation", "expires_at": "2025-12-31T10:05:00Z"}Errors
400 Bad Request— Invalid or expired request URI, JAR signature verification failure.401 Unauthorized— Missing or invalid JWT.
See also: Holder — parse the verifier’s request
POST /v1/oid4vp/presentation — Submit a VP Token presentation
Builds a VP Token for the selected credentials with selective disclosure and a Key Binding JWT, then submits it to the verifier’s response_uri via direct_post.
Auth: Bearer JWT · Content-Type: application/json
Request fields
authorization_request_uri(required, string) — Sameopenid4vp://URI used inrequest-details.selected_credentials(required, array) — Credentials to present:credential_id(required, UUID) — ID from holder storage.query_id(required, string) — DCQL credential query ID this satisfies.disclosed_claims(optional, array of strings) — Claim names to selectively disclose. If omitted, all claims are revealed.
holder_did(required, string) — Holder DID URL for Key Binding JWT signing.private_key(required, string) — Base64url encoded holder private key. Ed25519 fordid:iota; ES256 P-256 rawdor PEM fordid:key.
Response 200
{ "success": true, "redirect_uri": "https://verifier-client.example.com/callback?session_id=660e8400-e29b-41d4-a716-446655440001"}redirect_uri is present only when the verifier provided one at session creation.
Errors
400 Bad Request— No matching credentials, expired session, or Key Binding JWT signing failure.401 Unauthorized— Missing or invalid JWT.
See also: Holder — submit the presentation
Base URL: https://trust-registry.turingspace.co
The Trust Registry manages organization onboarding, DID creation, and DID Configuration Resources for domain linkage.
| Method | Path | Description | Auth |
|---|---|---|---|
| POST | /v1/auth/login | Authenticate with UUID and password for a JWT token. See Step 2: Log In. | Public |
| POST | /v1/did | Create an IOTA DID for the authenticated organization. See Step 3: Create a DID. | Bearer JWT |
| POST | /v1/did/import | Import an existing IOTA DID with ownership verification. See Step 4: Import an Existing DID (Alternative). | Bearer JWT |
| GET | /.well-known/did-configuration.json/:tenant | DIF Well-Known DID Configuration Resource for domain linkage verification. See DID Configuration. | Public |
POST /v1/auth/login — Authenticate as a Trust Registry organization
Authenticates an approved organization using their assigned UUID and password. Returns a Bearer JWT for subsequent DID management operations.
Auth: Public · Content-Type: application/json
Request fields
uuid(required, UUID v4) — Organization UUID assigned by TCS, e.g.a1b2c3d4-e5f6-7890-abcd-ef1234567890.password(required, string, min 8 chars) — Must contain at least one uppercase letter, one lowercase letter, and one digit, e.g.Password123.
Response 200
{ "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_in": 3600}Errors
400 Bad Request— Invalid UUID format or password policy violation.401 Unauthorized— Wrong UUID or password.
See also: Trust Registry — log in
POST /v1/did — Create an IOTA DID for your organization
Creates a new IOTA DID on behalf of the authenticated organization and returns the DID URL, key ID, public key JWK, and private key. The private key is returned only once.
Auth: Bearer JWT · Content-Type: application/json (empty body accepted)
Response 201
{ "did": "did:iota:0x1234567890abcdef...", "key_id": "did:iota:0x1234567890abcdef...#abc123", "public_key_jwk": { "kty": "OKP", "use": "sig", "key_ops": ["sign", "verify"], "alg": "EdDSA", "kid": "abc123", "crv": "Ed25519", "x": "abc123..." }, "private_key": "abc123..."}The private_key is returned only once. Save it immediately.
Errors
401 Unauthorized— Missing or invalid JWT.
See also: Trust Registry — create a DID
POST /v1/did/import — Import an existing IOTA DID
Associates an existing IOTA DID with the authenticated organization by verifying private key ownership.
Auth: Bearer JWT · Content-Type: application/json
Request fields
did(required, string) — IOTA DID URL, must start withdid:iota:, e.g.did:iota:testnet:0x7a8b9c....private_key(required, string) — Base64url encoded Ed25519 private key for ownership verification.
Response 201
{ "did": "did:iota:testnet:0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b", "method": "IOTA", "status": "active"}Errors
400 Bad Request— DID does not start withdid:iota:, or private key verification failed.401 Unauthorized— Missing or invalid JWT.
See also: Trust Registry — import a DID
GET /.well-known/did-configuration.json/:tenant — DIF Well-Known DID Configuration Resource
Returns the DIF Well-Known DID Configuration Resource for the named tenant. Domain linkage verifiers fetch this document to confirm that the domain is controlled by the DID owner. The response contains a linked_dids array of domain linkage credential JWTs.
Auth: Public · Content-Type: N/A (GET)
Path parameters
tenant(required, string) — Tenant identifier, e.g.turing.
Response 200
{ "@context": "https://identity.foundation/.well-known/did-configuration/v1", "linked_dids": [ "eyJhbGciOiJFZERTQSIsImtpZCI6ImRpZDppb3RhOjB4MTIzNC4uLiNrZXktMSJ9..." ]}Each entry in linked_dids is a signed Domain Linkage Credential JWT binding the tenant’s DID to this domain.
Errors — None expected. If no DIDs are registered for the tenant, linked_dids is an empty array (not a 404).
See also: Trust Registry — DID configuration
Base URL: https://schema-registry.turingspace.co
The Schema Registry serves VCT (Verifiable Credential Type) metadata documents that wallets and verifiers use to discover credential type information.
| Method | Path | Description | Auth |
|---|---|---|---|
| GET | /schemas | List all VCT schemas (paginated). See Discover a Credential Type. | Public |
| GET | /schemas/:type/:version | VCT Type Metadata Document for a credential type and version (SD-JWT VC VCT). See Discover a Credential Type. | Public |
| GET | /v1/schemas/:type/:version | Get VCT Type Metadata Document (deprecated — redirects to unversioned path) | Public |
GET /schemas — List VCT schemas (paginated)
Returns a paginated list of all published VCT schemas. Supports filtering by status, type name, and display name.
Auth: Public · Content-Type: N/A (GET)
Query parameters
status(optional, string) — Filter by lifecycle status, e.g.active.type(optional, string) — Partial case-insensitive match on credential type, e.g.University.name(optional, string) — Partial case-insensitive match on display name, e.g.University Degree.sort_by(optional, string) —updated_at(default) orcreated_at.offset(optional, integer) — Number of records to skip (default: 0).limit(optional, integer) — Max records to return.
Response 200
{ "data": [ { "name": "University Degree", "type": "UniversityDegree", "version": "v1", "status": "active", "updated_at": "2026-01-15T08:00:00.000Z" } ], "meta": { "offset": 0, "limit": 20, "total": 28 }}Errors — None expected under normal operation.
GET /schemas/:type/:version — VCT Type Metadata Document
Returns the SD-JWT VC Type Metadata Document for the given credential type and version. Wallets and verifiers discover this URL from the vct claim in issued credentials and fetch it for schema validation and display metadata.
Auth: Public · Content-Type: N/A (GET)
Path parameters
type(required, string) — Credential type identifier, e.g.UniversityDegree.version(required, string) — Schema version, e.g.v1.
Response 200
{ "vct": "https://schema-registry.turingspace.co/schemas/UniversityDegree/v1", "name": "University Degree", "description": "A credential issued by a university upon degree completion.", "schema": { "type": "object", "properties": { "degree": { "type": "string" }, "major": { "type": "string" } } }, "status": "active", "use_case": ["professional_qualification", "corporate_compliance"], "updated_at": "2026-01-15T08:00:00.000Z"}Errors
404 Not Found— VCT type or version not found.
GET /v1/schemas/:type/:version — VCT Type Metadata Document (deprecated)
Deprecated alias for GET /schemas/:type/:version. Returns an HTTP redirect (302) to the canonical unversioned path. Use GET /schemas/:type/:version directly.
Auth: Public · Content-Type: N/A (GET)
Response 302 — Always redirects to /schemas/:type/:version unconditionally (no existence check is performed).
Errors — None. The redirect fires regardless of whether the schema exists; a 404 may occur if the redirect target does not exist.
See also: the GET /schemas/:type/:version block above for the canonical (non-deprecated) endpoint.
What’s Next
Section titled “What’s Next”- Authentication — Understand API Key, JWT, and DPoP authentication patterns
- Architecture & Security — Review the system architecture and security model
- Standards Compliance — Explore the standards TCS implements