Skip to main content

JavaScript SDK

packages/sdk is a small TypeScript client for the REST API. Its one promise: it returns data only after the answer's signature, signer, deployment, time window, data age and nonce have all been checked against what the caller pinned and sent. If any check fails, it throws and returns nothing.

Not published, as of 2026-10-24

The package is @certichain/sdk version 0.1.0, marked private and not on npm. The name is a placeholder; the name and publishing are the project owner's decision. Inside the repository it is built with npm run sdk:build and tested with npm run sdk:test.

Module formatESM only
RuntimeNode.js 20 or later, or any runtime with fetch and Web Crypto
Dependenciesethers 6.14.4 and canonicalize 2.1.0, pinned exactly

Quick start​

import { createClient, UntrustedAnswerError } from "@certichain/sdk";

const client = createClient({
baseUrl: "https://certichain.example",
signer: "0x6666666666666666666666666666666666666666", // pinned, from the deployment owner
});

try {
const { data, standing, evidence } = await client.getCredential("0x3333333333333333333333333333333333333333333333333333333333333333");
if (standing.good) {
// found, active, not expired, issuer registered and not suspended
} else {
console.log(standing.reasons); // for example ["status-revoked"]
}
// keep `evidence` (the exact signed answer) if you need a record of what the chain held
} catch (error) {
if (error instanceof UntrustedAnswerError) console.error(`answer refused: ${error.reason}`);
else throw error;
}

Options​

OptionRequiredDefaultRule
baseUrlyeshttps, or http only for localhost, 127.0.0.1 and [::1]. No credentials, query or fragment.
signeryesThe API signer address you pinned. Without it an answer proves nothing.
chainIdnoDecimal string. Pin it when one signer could serve several deployments.
registrynoOne CredentialRegistry address, or a non-empty list (one per contract set)
apiKeynoPrintable text, no spaces, up to 512 characters. Sent as a bearer token to baseUrl only.
maxAnswerAgeSecondsno300Refuse answers generated longer ago (max 86,400)
maxDataAgeSecondsno60Refuse answers whose chain data may be older (max 3,600)
timeoutMsno10,000Per request
maxResponseBytesno1,048,576Larger answers are cut off with a NetworkError
fetch, nownoglobal fetch, system clockFor tests and special runtimes

Bad options throw ConfigError from createClient.

Calls​

CallEndpointReturnsExtra checks
getCredential(id)GET /credentials/{id}{ data, standing, evidence }id must be 0x plus 64 hex; the answer's id must match
verifyFile(text)POST /verify{ data, standing, evidence }1 to 65,536 bytes; data.fileSha256 must equal the SHA-256 of text, else UntrustedAnswerError("file")
getStatus()GET /status{ data, evidence }The answer's signer, chain and registry must match its meta and your pins

Each call makes a fresh 32-byte nonce. Requests use redirect: "error", credentials: "omit" and cache: "no-store".

Checks on every answer​

verifyEnvelope (also exported for callers who fetch answers themselves) runs, in order:

StepReason on failure
Exact envelope shape, times in YYYY-MM-DDTHH:MM:SSZ formshape
apiVersion is "1"version
Signature recovers to the address the answer names (65 bytes, last byte 1b or 1c)signature
That address is the pinned signersigner
Chain and registry match the pinschain, registry
Not past validUntil, not more than 60 seconds in the future, not older than maxAnswerAgeSecondsstale
dataMaxAgeSeconds within maxDataAgeSecondsdata-age
The nonce is echoedreplayed

The returned envelope is rebuilt from the signed parts only, so fields an intermediary added never reach your code. Details of the format: API envelope.

Standing​

credentialStanding(data, now) and fileStanding(verdict) turn data into { good, reasons }:

FunctionReasons
credentialStandingnot-found, issuer-not-registered, status-suspended, status-revoked, status-superseded, expired, issuer-suspended
fileStandingother-chain, not-found, anchor-mismatch, issuer-not-registered, signature-invalid, not-active, file-differs-from-claims, id-mismatch, expired, standard-proof-invalid, or not-fully-verified when no single check is named

credentialStanding also counts an expiry that fell after the answer was made when now is given (the client passes it). A file is good only when the server reports fullyVerified.

Errors​

All errors extend CertichainError.

ErrorWhenUseful fields
ConfigErrorBad options
ArgumentErrorBad call argument; nothing was sent
ApiErrorThe API answered with an errorcode, status, serverMessage (control characters removed, at most 200 characters, the API key redacted), retryAfterSeconds (only a plain 1 to 5 digit value)
NetworkErrorNo answer, timeout, or answer too largekind: transport, timeout or too-large; causeName
UntrustedAnswerErrorAn answer failed a check; no data returnedreason: shape, version, signature, signer, chain, registry, stale, data-age, replayed or file

An error body that does not match the documented shape becomes ApiError with code UNEXPECTED_RESPONSE.

Example script​

packages/sdk/examples/verify-credential.mjs checks one credential, and optionally a file:

CERTICHAIN_URL=https://certichain.example CERTICHAIN_SIGNER=0x6666666666666666666666666666666666666666 \
node examples/verify-credential.mjs <credential id> [credential-file.json]

CERTICHAIN_API_KEY is optional. Exit codes: 0 good standing, 2 not in good standing, 1 the answer was not trusted or the call failed.

Source files​

  • packages/sdk/src/ (client.ts, options.ts, transport.ts, envelope.ts, canonical.ts, data.ts, standing.ts, errors.ts, types.ts, index.ts)
  • packages/sdk/examples/verify-credential.mjs
  • packages/sdk/README.md

Tests​

  • packages/sdk/test/ (boundaries, client, envelope, example, openapi-binding, standing)
  • The sdk CI job; see Interop and SDK tests
  • E2E: e2e/journeys/verification-api.journey.ts runs the SDK against the real handlers