Every endpoint, parameter, and response schema for Nexiel Credentials.
Requests to a production deployment require a bearer token. Mint one via the standard OAuth2 client-credentials grant against https://auth.nexiel.io/realms/nexiel/protocol/openid-connect/token using the client_id/client_secretpair from your dashboard's API Keys page — see this API's own quickstart for the exact curl command.
/health/v1/credentials/.well-known/openid-credential-issuer/v1/credentials/sandbox/demo-offerNever available in production mode. Every call ensures a real, APPROVED, branded sandbox academic issuer/template/batch exist (idempotent), then issues a genuinely fresh, real, single-use offer -- the exact same @nexiel/credential-issuance offer/redemption/signing path every real institution batch uses, against synthetic data only.
/v1/credentials/tokengrant_type"urn:ietf:params:oauth:grant-type:pre-authorized_code"requiredpre-authorized_codestringrequired/v1/credentialsformat"dc+sd-jwt"requiredproofobjectrequiredproof_type"jwt"requiredjwtstringrequired/v1/credentials/issue/templatesBearer auth (production mode)In production mode, results are always scoped to the caller's own academicIssuerId (tenant-scope.ts) regardless of the academicIssuerId query filter -- passing a DIFFERENT issuer's id 404s exactly like an unknown one would (never a 403, never a silently-ignored filter that could be mistaken for "this issuer just has no templates"). Omitting the filter lists only the caller's own templates, never every issuer's. Sandbox mode performs no such scoping (no real per-client identity exists there at all).
academicIssuerId (query)stringstatus (query)string/v1/credentials/issue/templatesBearer auth (production mode)In production mode, academicIssuerId MUST be the institution the caller's own API key was issued against (tenant-scope.ts resolves this from the caller's Keycloak client_id) -- naming a DIFFERENT (even a real, APPROVED) issuer's id 404s exactly like an unknown one would, never a 403 that would confirm that issuer exists. Sandbox mode performs no such check (no real per-client identity exists there at all).
academicIssuerIdstringrequiredcredentialType"ENROLMENT" | "MODULE_COMPLETION" | "DEGREE_AWARD"requiredclaimsSchemaunknownrequiredA JSON Schema describing this credential's claims.
sdJwtDisclosureFrameunknownrequiredRFC 9901 disclosure-frame shape (e.g. { "_sd": ["degreeTitle", "awardDate"] }) -- claim names here must never include a protected top-level SD-JWT VC claim (iss/vct/iat/exp/nbf/cnf/sub/status).
/v1/credentials/issue/templates/{id}/submitBearer auth (production mode)In production mode, only submittable by the template's OWN issuer's API key -- a template that exists but belongs to a different issuer 404s exactly like an unknown template id would (tenant-scope.ts + templates.ts's submitTemplateForReview, same IDOR-safe convention as every other scoped route in this app). Sandbox mode performs no such check.
id (path)stringrequired/v1/credentials/issue/templates/{id}/reviewBearer auth (production mode)id (path)stringrequireddecision"APPROVED" | "REJECTED"requirednotesstring/v1/credentials/issue/batchesBearer auth (production mode)In production mode, academicIssuerId MUST be the institution the caller’s own API key was issued against (same cross-tenant scoping as the template routes) -- naming a different issuer 404s exactly like an unknown one would. Requires the credentials-api-issuer capability role, not just credentials-api-client.
academicIssuerIdstringrequiredcredentialTemplateIdstringrequiredpreparedBySignatoryIdstringrequiredsubmissionMethod"CSV_UPLOAD" | "API"requiredcsvContentstringRequired (and only meaningful) when submissionMethod is "CSV_UPLOAD".
rowsobject[]Required (and only meaningful) when submissionMethod is "API".
studentFullNamestringrequiredstudentEmailstringrequiredstudentDobstringinstitutionStudentIdstringclaimValuesobject<string, unknown>required/v1/credentials/issue/batches/{id}Bearer auth (production mode)id (path)stringrequired/v1/credentials/issue/batches/{id}/items/{itemId}/recheckBearer auth (production mode)id (path)stringrequireditemId (path)stringrequiredrecheckingSignatoryIdstringrequiredclaimValuesobject<string, unknown>required/v1/credentials/issue/batches/{id}/approveBearer auth (production mode)Requires a real, freshly-verified step-up assertion in the x-credentials-step-up-assertion header (see step-up.ts). The dashboard UI that produces this via a genuine Keycloak re-authentication round trip is Phase 5’s job -- this route and approveCredentialBatch both independently verify freshness server-side.
id (path)stringrequiredapprovedBySignatoryIdstringrequired/v1/credentials/verify/sessionsBearer auth (production mode)Scoped to the calling employer: in production, the bearer token's authenticated identity -- `clientId` in the query string is IGNORED in that case. Never consumes disclosedClaims' one-time delivery, and never triggers a live revocationStatus re-check per row (reports each decision's own last-observed value instead) -- both deliberate "a bulk read must never have expensive/consuming side effects" choices, mirroring apps/verify-api's own LIST route precedent for the AML evidence pack.
clientId (query)stringlimit (query)string/v1/credentials/verify/sessionsBearer auth (production mode)Requires a credentials-api-verifier-scoped API key (an ordinary employer verification key, gated by KYB approval -- never a credentials-api-client or credentials-api-issuer key, both of which are issuer-side and structurally rejected here). Reuses the exact same OpenID4VP session pattern @nexiel/verify already implements for Nexiel Verify, parameterized by checkType (academic-credential | academic-enrolment-status).
clientIdstringcheckType"academic-credential" | "academic-enrolment-status"required/v1/credentials/verify/sessions/{sessionId}/responsesThis route's real authorization is the session's own unguessable state/nonce (OpenID4VP direct_post) -- a wallet holds no Nexiel API key, so no bearer token is required or checked here, exactly like apps/verify-api's identical wallet-callback route.
sessionId (path)stringrequiredstatestringrequiredvp_tokenobject<string, string | string[]>required/v1/credentials/verify/sessions/{sessionId}Bearer auth (production mode)The real delivery channel for disclosedClaims (never the wallet's own POST .../responses callback reply) -- delivered at most once (see `claimsDelivery` query parameter). revocationStatus is re-derived LIVE on every single read of THIS route, never cached -- see @nexiel/verify's status-list.ts.
claimsDelivery (query)stringControls whether THIS request may consume the session's one-time disclosed-claims delivery -- mirrors apps/verify-api's identical `evidencePackDelivery` query parameter exactly. "claim" (the default) is the real, client-facing delivery path. "peek" NEVER claims it: `decision.disclosedClaims` is always null in this mode, and the session's one-time delivery window is left untouched.
sessionId (path)stringrequired