SDKs
The SDKs are thin clients for the public API: one method per endpoint, typed responses and a single error type. They do the routine work for you: the base URL, encoding identifiers, timeouts and turning error responses into exceptions.
The SDKs add no checks of their own. Every result comes from the API and means exactly what the API page for that endpoint says. Anything the SDKs do not cover, you can call over HTTP.
- Base URL https://did.is/api
- Percent-encodes identifiers once
- 30-second timeout by default
- Non-2xx answers become DidisError
What you get
Section titled “What you get”| TypeScript | Python | |
|---|---|---|
| Package | @didis/client 0.2.5 |
didis-py 0.2.5 (import didis) |
| Source | did-is/didis-js | did-is/didis-py |
| Runtime | Any runtime with fetch: Node 18+, Deno, Bun, browsers, edge. Zero dependencies, ESM. |
Python 3.11+, httpx. Sync DidisClient and async AsyncDidisClient. |
| Responses | Typed objects | Plain dicts typed with TypedDict (didis.types) |
| Timeout | new DidisClient({ timeoutMs }), default 30 000 |
DidisClient(timeout=…), default 30.0 seconds |
What they do not do
Section titled “What they do not do”- Retries beyond
Retry-After. From 0.2.4 the SDKs wait forRetry-Afterand retry a429or503up to twice (at most 10 seconds per wait). Other errors raiseDidisErrorat once. Passretry: false(TypeScript) orretry=False(Python) to handle every retry yourself (see Errors and rate limits). - Directory and claims. These endpoints have no SDK method; call them over HTTP (see the API reference).
- Gateway middleware. There is no ready-made proxy or framework plugin. To guard individual tool handlers, use the SDK guard; Guard agent tools shows how with MCP clients, LangChain and the OpenAI Agents SDK.
Install
Section titled “Install”npm install @didis/clientpip install didis-py# No official Rust SDK: use any HTTP client.cargo add reqwest --features jsoncargo add serde_jsoncargo add tokio --features fullBasic use
Section titled “Basic use”import { DidisClient, DidisError } from "@didis/client";
const didis = new DidisClient();
try { const r = await didis.resolve("did:web:did.is"); console.log(r.verdict.headline); for (const d of r.dimensions) console.log(d.label, d.state);} catch (e) { if (e instanceof DidisError) console.log(e.status, e.problem.code, e.problem.detail); else throw e;}from didis import DidisClient, DidisError
with DidisClient() as didis: try: r = didis.resolve("did:web:did.is") print(r["verdict"]["headline"]) for d in r["dimensions"]: print(d["label"], d["state"]) except DidisError as e: print(e.status, e.problem.get("code"), e.problem.get("detail"))#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let r: serde_json::Value = reqwest::get("https://did.is/api/v1/resolve/did%3Aweb%3Adid.is") .await? .error_for_status()? .json() .await?; println!("{}", r["verdict"]["headline"]); Ok(())}Async Python:
import asynciofrom didis import AsyncDidisClient
async def main() -> None: async with AsyncDidisClient() as didis: r = await didis.resolve("did:web:did.is") print(r["verdict"]["outcome"])
asyncio.run(main())In Rust, percent-encode the identifier once, and on non-2xx responses read the RFC 9457 problem body and the Retry-After header yourself (see Errors).
Using a project API key
Section titled “Using a project API key”Without a key, every call uses the anonymous public API and its per-client budget. With a project API key from the console (SDK 0.2.5 or later), four methods count against your team’s monthly allowance instead: inspectMcp, inspectA2a, authorize and verifyDelegation (inspect_mcp, inspect_a2a, authorize, verify_delegation in Python). The results are the same as without a key.
import { DidisClient } from "@didis/client";
const didis = new DidisClient({ apiKey: process.env.DID_IS_API_KEY });
const decision = await didis.authorize(chain, "payments:refund");const usage = await didis.usage();console.log(usage.expensiveUsed, "of", usage.expensiveLimit, "used in", usage.period);import osfrom didis import DidisClient
with DidisClient(api_key=os.environ["DID_IS_API_KEY"]) as didis: decision = didis.authorize(chain, "payments:refund") usage = didis.usage() print(usage["expensiveUsed"], "of", usage["expensiveLimit"], "used in", usage["period"])- The key is sent only for those calls and for
usage(). All other methods, andverifyDelegationwithregister: true, keep using the public API without the key. - Keep the key server-side. Read it from an environment variable or a secret store. Never put it in browser or mobile code.
- No double billing. Each call carries a fresh idempotency key, and the automatic retries reuse it, so a retried request is counted once.
usage()returns this month’s allowance for the whole team (all projects share it):period, plusUsed,Reserved,LimitandRemainingfor thestandardandexpensivepools. It needs a key and is not counted.- When the allowance is used up, these calls raise
DidisErrorwith status429and codeQUOTA_EXHAUSTEDuntil the next month or a plan change. Retrying does not help. Remove the key to fall back to the anonymous public API.
The team’s owners also get an email at 80% and at 100% of each monthly allowance (see Accounts, projects and plans).
Reading the result
Section titled “Reading the result”| What happens | What it means | What to do |
|---|---|---|
| The method returns | The API answered 2xx. | Read the result fields, for example verdict or status. |
DidisError, status 429 or 503 |
Budget used up, or service busy. problem.retryAfter holds the Retry-After value. |
Wait that long, then retry. |
DidisError, other 4xx |
The request itself is wrong, or the method or feature is not supported. | Fix the request; problem.code says why. Do not retry unchanged. |
DidisError, 5xx |
DID.is or the remote host failed. | Retry later. |
| Timeout | No answer within the client timeout. | Retry later, or raise the timeout. |
Methods
Section titled “Methods”| TypeScript | Python | Endpoint |
|---|---|---|
health() |
health() |
GET /health |
resolve(did, { noCache }) |
resolve(did, no_cache=…) |
GET /v1/resolve/{did} |
resolveW3c(did) |
resolve_w3c(did) |
GET /1.0/identifiers/{did} |
dereference(didUrl) |
dereference(did_url) |
GET /v1/dereference/{didUrl} |
stream(did) |
stream(did) |
GET /v1/stream/{did} |
history(did) |
history(did) |
GET /v1/history/{did} |
diff(did, from?, to?) |
diff(did, …) |
GET /v1/diff/{did} |
graph(did) |
graph(did) |
GET /v1/graph/{did} |
verifyCredential(vc, { expectedAudience }) |
verify_credential(vc, …) |
POST /v1/credentials/verify |
evaluatePolicy(did, policy, vc?) |
evaluate_policy(did, policy, credential=None) |
POST /v1/policies/evaluate |
inspectMcp(endpoint) |
inspect_mcp(endpoint) |
GET /v1/mcp/inspect |
inspectA2a(url) |
inspect_a2a(url) |
GET /v1/a2a/inspect |
verifyDelegation(chain, { trustedRoots, register }) |
verify_delegation(chain, trusted_roots=…, register=…) |
POST /v1/agents/verify-delegation |
authorize(chain, tool, { trustedRoots }) |
authorize(chain, tool, trusted_roots=…) |
POST /v1/agents/authorize |
verifyTool(agent, tool, root?) |
verify_tool(agent, tool, root=None) |
GET /v1/agents/verify-tool |
fastVerifyDid(did) |
fast_verify_did(did) |
GET /v1/fast-verify/did/{did} |
fastVerifyJws(jws, relationship?) |
fast_verify_jws(jws, relationship=None) |
POST /v1/fast-verify/jws |
usage() (needs a key) |
usage() (needs api_key) |
Team allowance this month |
verifyCustomerMonitorSignature(…) |
verify_customer_monitor_signature(…) |
Webhook signatures |
No CLI
Section titled “No CLI”There is no published DID.is command-line tool. Use curl or the SDKs.
Next steps
Section titled “Next steps”- API overview for budgets and conventions.
- Resolution for your first real call.