This document is identical in every Didit server SDK repository: sdk-python, sdk-node, sdk-go, sdk-java, sdk-php, sdk-ruby and sdk-dotnet.
It is the contract that keeps the seven SDKs consistent.
A change to it is made in all seven repositories, in the same words.
Where this document and an SDK disagree, the SDK is wrong.
Version 1, 2026-10-17.
A backend developer integrates Didit in minutes, in their own language, with an SDK that feels native to that language. The SDK covers the whole public server-side API, with real types for every request and response, and it stays current as the API changes. Quality bar: the official Stripe, OpenAI and Twilio libraries. The SDKs are thin, predictable and boring: no hidden magic, no framework lock-in, minimal dependencies.
| Language | Repository | Package | Import / namespace | Minimum runtime |
|---|---|---|---|---|
| Python | didit-protocol/sdk-python |
PyPI didit-sdk (see 2.1) |
import didit |
Python 3.10 |
| TypeScript / JavaScript | didit-protocol/sdk-node |
npm @didit-protocol/sdk-node |
import Didit from "@didit-protocol/sdk-node" |
Node 18 (also Bun, Deno, edge runtimes with fetch) |
| Go | didit-protocol/sdk-go |
module github.com/didit-protocol/sdk-go |
package didit |
Go 1.22 |
| Java / Kotlin | didit-protocol/sdk-java |
Maven me.didit:didit-java |
me.didit |
Java 11 |
| PHP | didit-protocol/sdk-php |
Packagist didit-protocol/sdk-php |
Didit\ |
PHP 8.1 |
| Ruby | didit-protocol/sdk-ruby |
RubyGems didit-sdk (the name didit is taken) |
require "didit", module Didit |
Ruby 3.1 |
| C# / .NET | didit-protocol/sdk-dotnet |
NuGet Didit |
namespace Didit |
.NET 8 and .NET Standard 2.0 |
The repository names follow the existing client SDKs (sdk-web, sdk-ios, sdk-android, sdk-flutter, sdk-react-native).
The Maven group me.didit is the one sdk-android already publishes under.
PyPI didit belongs to an unrelated project from 2013.
PyPI didit-sdk is a community package by Agustin Saiz (MIT, unofficial, sessions only).
He transferred the didit-sdk project to Didit, and the official SDK published 1.0.0 under that name on 2026-10-29.
As agreed with him, his releases 0.1.0 to 0.4.0 stay published, his PyPI user stays on the project, and the 1.0.0 release notes credit the initial community versions.
Every SDK is generated from Didit's published OpenAPI documents and checked against them in CI.
File in openapi/ |
Upstream | Base URL |
|---|---|---|
verification.json |
https://docs.didit.me/openapi-25.json (mintlify-docs openapi-25.json, branch main) |
https://verification.didit.me |
account.json |
https://docs.didit.me/openapi-auth.json (mintlify-docs openapi-auth.json) |
https://apx.didit.me/auth/v2 |
authentication.json |
https://docs.didit.me/openapi-authentication.json (mintlify-docs openapi-authentication.json) |
the application's issuer (section 10) |
overlay.json |
this repository | - |
webhook-vectors.json |
generated from service-didit-verification/src/sessions/utils/webhooks.py |
- |
SOURCE.json |
URL, SHA-256 and fetch time of each upstream file | - |
The upstream files are copied byte for byte and never edited by hand.
overlay.json holds the SDK naming (section 4) for operations whose upstream definition does not carry it yet.
When an operation carries x-didit-sdk upstream, the upstream value wins and the overlay entry is deleted.
authentication.json describes the Authentication runtime (section 10). The authentication module stays hand-written, because token verification needs code a generator cannot produce; a contract test (section 11) keeps it aligned with the document.
Every repository has .github/workflows/spec-sync.yml, run daily and on workflow_dispatch.
mintlify-docs starts it in all seven repositories through workflow_dispatch as soon as docs.didit.me serves a changed OpenAPI document (server-sdk-spec-sync.yml there); mintlify-docs main documents only what production serves, so no SDK gets an operation or field before the API serves it.
It downloads the upstream files, and when any of them changed it regenerates the code, runs the full test suite and opens a pull request titled <type>(spec): sync API spec YYYY-MM-DD.
The type follows section 13 and is the same in every repository: feat(spec) when the sync adds or changes anything a caller can observe (an operation, parameter, field, enum value or response shape), a minor release; chore(spec) when only descriptions, examples or other documentation changed, which releases nothing on its own.
The commit never carries ! or a BREAKING CHANGE: footer: a change that mirrors the API is never a major version (section 13).
The pull request body opens with a "Breaking changes" section, then lists added, removed and changed operations and schemas.
"Breaking changes" lists every change that can break typed code, or says "None detected": a field or parameter whose type changed, a field that can now be null, a removed operation, field or enum value, and a newly required parameter or request field.
Every repository detects exactly that list, so the seven pull requests for one upstream change report the same breaking changes.
The rules, checked in every repository by the shared vectors in openapi/breaking-vectors.json (generated in sdk-python and copied byte for byte into the other six; a rule change lands there first):
- Responses (the body of every 2xx status): a removed field, a field whose type changed, a field that can now be null or is no longer required, and a removed enum value count. A typed string format (
date,date-time,binary) gained, lost or changed, and a typed value becoming untyped or the reverse, are type changes. An added field or enum value is listed, not counted: enums are open and unknown values must be accepted. - Requests (path, query and header parameters, and the body): a removed parameter or field, a parameter or field whose type changed, a new required parameter or field, an optional one becoming required, a body becoming required, and a removed enum value count. A parameter or field that can now be null or stops being required is listed, not counted, because it only widens what a caller may send.
- Operations: a removed operation counts when its SDK method goes; a moved operation's changes count under its new path; an added operation is listed. Operations with
server: falseare listed and never counted. - Unions (
oneOf,anyOf): each old variant is paired with the variant it became, and changes inside a paired variant follow the rules above even when other variants are added, removed or reordered. A renamed variant title is listed, not counted. - Success statuses: a method returns every 2xx body. An added 2xx status without a body counts as "can now be null"; a removed status whose body no other 2xx status returns counts as a type change; any other status change is listed.
- Every counted change reaches the release notes, with no cap.
The release notes repeat that list under "API changes that can break typed code".
A new server operation without SDK naming fails the generator with a message that names the operation; the fix is an entry in
overlay.json(or upstream). The sync never deletes a public SDK method silently: a removed operation is marked deprecated for one minor version first, and the pull request says so.
Every operation has an x-didit-sdk object:
{"resource": "sessions.imports", "method": "create", "auth": "api_key", "server": true, "scope": "application"}resource: dot path of snake_case resource names; each segment becomes a nested resource accessor.method: snake_case method name on that resource.auth:api_key(headerx-api-key),account_token(Bearer fromaccount.login),none,device_tokenorpublic.server:falsefor operations that use an end-user or device token (Session-Token,X-Transaction-Token, case-request tokens, Travel Rule pickup and widget links). Server SDKs do not expose them.scope: "application": the path starts with/v3/organization/{organization_id}/application/{application_id}/. The SDK fills both ids from the client options, so methods never take them as arguments. Calling such a method without both options raises a configuration error before any request is sent.
Convert names to the language's idiom and nothing else:
| Language | Example: sessions.imports.create |
|---|---|
| Python | client.sessions.imports.create(...) and await client.sessions.imports.create(...) on AsyncDidit |
| TypeScript | await didit.sessions.imports.create({...}) |
| Go | client.Sessions.Imports.Create(ctx, params) |
| Java | client.sessions().imports().create(params) plus createAsync returning CompletableFuture |
| PHP | $didit->sessions->imports->create([...]) |
| Ruby | didit.sessions.imports.create(...) |
| C# | await client.Sessions.Imports.CreateAsync(...) |
The full resource tree is generated from openapi/*.json: the upstream x-didit-sdk naming plus the overlay.json entries upstream does not carry yet.
Highlights: sessions, sessions.imports, sessions.reviews, the standalone APIs (id_verification.verify, face_match.compare, face_search.search, passive_liveness.check, age_estimation.estimate, proof_of_address.verify, document_ai.analyze, aml.screen, database_validation.validate, phone.send_code, email.check_code, ip.assess_risk, kyb.search), id_wallets, fraud_checks, users, businesses, transactions, transactions.rules, transactions.travel_rule, wallet_screening, travel_rule, travel_rule.wallet_addresses, workflows, questionnaires, webhooks.destinations, billing, biometric_templates, customization, cases (with notes, links, checklist, requests, teams, routing_rules, blueprints, regulatory_reports), regulatory_reports, report_templates, networks, and account (account.organizations, account.applications).
Two layers, kept strictly apart.
Generated layer (resources/ and types/ or the language equivalent): one class or module per resource, one method per operation, one type per request and response schema.
Every generated file starts with Code generated by codegen from openapi/*.json. DO NOT EDIT.
CI regenerates and fails when the result differs from what is committed.
The generator lives in the repository (codegen/), is written in the SDK's own language or in Python, and reads only openapi/.
Core layer (hand-written, small, fully tested): configuration, HTTP transport, authentication headers, retries, timeouts, errors, pagination, multipart and binary bodies, webhook verification (section 9) and the Authentication helpers (section 10).
- A
$refcomponent keeps its component name in the language's case (SessionDecision). - An inline request body becomes
<Resource><Method>Params(SessionsImportsCreateParams); an inline response becomes<Resource><Method>Response. - Inline nested objects take their parent's name plus the property name (
SessionsCreateParamsContactDetails). - Enums are open: unknown values from the API must never raise. Python uses
Literal[...] | str; TypeScript a string union plus(string & {}); Go, Java and C# a string-backed type with constants for the known values; PHP string constants; Ruby frozen string constants. - Unknown response fields are preserved and readable (forward compatibility), never dropped and never an error.
- Distinguish absent from
nullwhere the language allows it (PythonNotGiven, TypeScript optional versus| null, GoOpt[T], JavaOptional/JsonNullable, C#Optional<T>). - Date-time strings are exposed as the language's date-time type; money and score fields keep the spec's type (string decimals stay strings).
Didit(api_key, base_url?, organization_id?, application_id?, timeout = 60s, max_retries = 2, default_headers?, http_client?)
api_keyfalls back to theDIDIT_API_KEYenvironment variable;organization_idandapplication_idtoDIDIT_ORGANIZATION_IDandDIDIT_APPLICATION_ID.base_urldefaults tohttps://verification.didit.me;account_base_urltohttps://apx.didit.me/auth/v2.- Every option can be overridden per request (
with_options(...)or a request options argument). - Headers on every request:
x-api-key,User-Agent: didit-<language>/<version> (<runtime> <version>; <os>),X-Didit-Client: <language>/<version>,Accept: application/json. - The API key is never logged, never included in an error message and never sent to any origin other than
base_url(a paginationnextURL on another origin is refused). - Optional debug logging (
DIDIT_LOG=debug) prints method, path, status, duration and request id, never bodies or headers. - Thread-safe and reusable: one client per process is the documented pattern.
| Error | When |
|---|---|
DiditError |
base of everything below |
APIConnectionError / APITimeoutError |
no response |
APIStatusError |
any non-2xx, with status, body (parsed JSON when possible), headers, request_id |
BadRequestError 400, AuthenticationError 401, PermissionDeniedError 403, NotFoundError 404, ConflictError 409, UnprocessableEntityError 422, RateLimitError 429, InternalServerError 5xx |
subclasses of APIStatusError |
WebhookVerificationError |
section 9, with a machine-readable reason |
ConfigurationError |
missing API key, missing organization or application id |
Retries: up to max_retries (default 2), exponential backoff starting at 0.5 s with full jitter, capped at 8 s, honouring Retry-After (seconds or HTTP date, capped at 60 s).
Retry on connection errors, timeouts, 408, 429 and 5xx for GET, HEAD, PUT and DELETE.
Never retry POST or PATCH after the request may have reached the server (a 5xx or a read timeout); retry them only on a connection that failed before sending, or on 429.
Every response exposes the raw HTTP response (status, headers, request id) through the language's usual pattern (with_raw_response, .asResponse(), a Response field).
- Lists use
limitandoffsetand return{count, next, previous, results}. The SDK returns a page object with those fields plus auto-pagination: Pythonfor x in client.sessions.list(...)andasync for, TypeScriptfor await, Go an iterator (iter.Seq2), JavaIterableandautoPager(), PHP and Rubyeach/auto_paging_each, C#IAsyncEnumerable. - Multipart operations (standalone APIs with images, document uploads, face uploads) accept a path, bytes or a stream, with an optional file name and content type.
- Binary responses (PDF reports, network reports, wallet screening PDFs) return bytes or a stream, never parsed JSON.
webhooks.verify(raw_body, headers, secret, tolerance = 300, allow_simple = false) returns the typed event or raises WebhookVerificationError.
secret may be one secret or a list (rotation).
A second entry point, webhooks.verify_parsed(payload, headers, secret), exists for frameworks that only expose the parsed JSON body; it can check X-Signature-V2 only.
Ship the verifier in the core with no framework dependency; framework examples (FastAPI, Django, Flask, Express, Next.js, Gin, Spring Boot, Laravel, Symfony, Rails, ASP.NET Core) live in examples/, not in the package.
From build_webhook_signature_headers in service-didit-verification/src/sessions/utils/webhooks.py:
- The body is
json.dumps(shorten_floats(payload), sort_keys=True, separators=(",", ":"))in UTF-8, with Python's defaultensure_ascii=True(non-ASCII as\uXXXX). X-Signature: lowercase hex HMAC-SHA256 of the exact body bytes.X-Signature-V2: lowercase hex HMAC-SHA256 of the canonical JSON (9.2). Recommended, because it survives middleware that re-encodes the body.X-Signature-Simple(deprecated, envelope only): HMAC-SHA256 of"{timestamp}:{session_id}:{status}:{webhook_type}", missing fields as empty strings.- During a secret rotation the same three signatures made with the previous secret arrive as
X-Signature-Previous,X-Signature-V2-PreviousandX-Signature-Simple-Previous. X-Timestamp: unix seconds, equal to the body'stimestamp.- The HMAC key is the secret's UTF-8 bytes.
Parse the body, then serialise it exactly like Python's json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) after shorten_floats:
- Objects: keys sorted by Unicode code point (not UTF-16 code unit order), written as
{"k":v,...}with no whitespace. - Strings: escape
"as\",\as\\, and\n,\r,\t,\b,\fwith their short escapes; every other character below U+0020 as\u00XXwith lowercase hex; everything else literal UTF-8 (including/, U+007F, U+2028 and U+2029). - Numbers: a float with an integral value is written as an integer (
87.0becomes87,1e16becomes10000000000000000); integers in plain decimal; other floats in Pythonreprform, which is the shortest round-trip digits with scientific notation when the decimal exponent is below -4 or at least 16, written1e-05,1e-07,1.5e-05(sign always, at least two exponent digits). true,false,nullas is.
Implementations must parse numbers losslessly where the language allows it (Go json.Number, Java BigDecimal, C# JsonElement raw text, Python and Ruby native), so integers above 2^53 survive.
In TypeScript, canonicalise from the raw body with a token-preserving parser, because JSON.parse loses precision on such integers.
- Read the timestamp from the body's
timestamp(an integer); ifX-Timestampis present it must be equal (timestamp_mismatch). - Reject when
|now - timestamp| > tolerance(timestamp_out_of_tolerance). - For each secret, accept when
X-Signaturematches the HMAC of the raw body, orX-Signature-V2matches the HMAC of the canonical JSON, or their-Previousvariants match. X-Signature-Simple(and its-Previous) counts only withallow_simple = true; even then the result says the body is unauthenticated, and the docs tell the receiver to re-fetch the record from the API.- Compare in constant time, and only well-formed 64-character lowercase hex values.
- No signature header at all is
missing_signature; no match isinvalid_signature; an empty secret is aConfigurationError.
openapi/webhook-vectors.json holds vectors produced by running the backend's own signing functions.
Every SDK test suite runs every vector and asserts the expected outcome, and asserts its canonical JSON equals canonical_v2 byte for byte.
Parse the verified body into a discriminated union on webhook_type: status.updated, data.updated, user.status.updated, user.data.updated, business.status.updated, business.data.updated, activity.created, transaction.created, transaction.status.updated, travel_rule.status.updated, workflow.compliance_changed, kyb.registry_search.resolved, plus an UnknownEvent that keeps the raw payload for any other type.
Envelope fields on every event: event_id, webhook_type, timestamp, created_at, application_id, environment (live or sandbox).
Session events add session_id, business_session_id, session_kind, status, workflow_id, workflow_version, vendor_data, metadata, decision, resubmit_info; transaction events add transaction_id, txn_id, status, score, severity, amount, currency, direction, action_required; Travel Rule events add transaction_id, txn_id, travel_rule_status, rail, direction; user and business events add vendor_user_id or vendor_business_id, vendor_data, status.
Where the spec has a schema for the payload (for example the session decision), reuse the generated type.
Didit Authentication gives each application its own OpenID Connect issuer for the application's end users: hosted sign-in pages, passwords, codes, magic links, passkeys, social and enterprise SSO, MFA, organizations, step-up verification and agent access.
Source: service-didit-auth/src/authentication/ (README, urls.py, runtime_urls.py, host_urls.py) on development; docs in mintlify-docs authentication/ and quickstarts/.
It is in early access and reaches production in the 2026-11-01 release.
The server SDKs ship an authentication module for the customer's backend.
It never takes the API key: it talks to the application's issuer (https://<slug>.auth.didit.me or the customer's own domain), whose discovery document is /.well-known/openid-configuration.
Authentication(issuer)orAuthentication.from_publishable_key("pk_live_..." | "pk_test_...")(the key carries the base64url issuer).- Discovery and JWKS fetched lazily and cached, honouring
Cache-Control(5 minutes today); an unknownkidtriggers at most one refetch per minute. verify_access_token(token, audience?): ES256 only (rejectnone, HS*, RS* and any other algorithm), headertypat+jwt,issequal to the issuer,audwhen given,exp,nbfandiatwith 60 s leeway. Returns typed claims:iss,aud,client_id,sub,sid,jti,iat,exp,vendor_data,amr,org(id,role),verified,act.verify_id_token(token, nonce, audience): same checks plusnonce; claims addauth_time,email,email_verified,name,phone_number.create_pkce()(S256 verifier and challenge),create_state()andcreate_nonce().exchange_code(code, code_verifier, redirect_uri)andrefresh(refresh_token)againstPOST token/; document that refreshes for one session must be serialised, because reusing a consumed refresh token revokes the session.get_session(access_token)(GET session/) for instant revocation checks, andrevoke_session(access_token)(DELETE session/).create_step_up(access_token, action, amount?, currency?, return_to?)(POST step-up/) andretrieve_step_up(access_token, id); document that the backend acts only on statusapproved.- Browser-only routes (hosted pages, password, email and phone codes, passkeys, MFA, social, SSO) are out of scope.
- The console management API for Authentication uses console sessions, not API keys, so it is out of scope until it accepts API keys.
- Authentication events do not exist as webhooks yet; do not invent
auth.*webhook types.
Tests use a locally generated ES256 key pair and a stubbed issuer; never call a real issuer in unit tests.
- Unit tests for the core: configuration, headers, errors per status, retry policy (including the no-retry cases for
POST),Retry-After, timeouts, pagination, multipart, binary responses, origin check, redaction. - Generated tests: one test per server operation from the spec's examples, with a mocked HTTP layer, asserting method, path (ids filled, application scope expanded), query, body encoding and response parsing.
- Contract test: every
server: trueoperation inopenapi/has exactly one SDK method, and nothing else exists. Forauthentication.jsonthe methods are the hand-written ones of section 10, so a spec sync that adds, removes or renames an Authentication operation fails until the module follows. - Webhook vectors (9.3) and Authentication token tests (10).
- Optional live smoke tests against a sandbox application, run only when
DIDIT_SANDBOX_API_KEYis set; never in default CI; they create nothing that costs money beyond sandbox sessions. - Strict static checks: Python
mypy --strictandpyright, ruff; TypeScriptstrictwith noanyin public types, eslint; Gogo vet,staticcheck,golangci-lint; Java-Werrorwith errorprone and nullness annotations; PHP PHPStan level max and PHP-CS-Fixer; Ruby RBS signatures checked with Steep, RuboCop; C# nullable enabled with warnings as errors. - CI matrix over every supported runtime version on Linux, plus macOS and Windows where the language is commonly used there.
- Coverage of the core at 90 % or more; the number is not a substitute for behaviour tests.
Minimal and mainstream.
Python: httpx, pydantic>=2, typing-extensions, and cryptography only for the Authentication module (optional extra didit-sdk[auth]).
TypeScript: no runtime dependencies (fetch, crypto.subtle).
Go: standard library only.
Java: java.net.http and Jackson.
PHP: PSR-18 and PSR-17 with discovery, Guzzle as the suggested default, firebase/php-jwt or an equivalent only for Authentication.
Ruby: net/http from the standard library, jwt only for Authentication.
.NET: System.Net.Http, System.Text.Json, Microsoft.IdentityModel.Tokens only for Authentication.
- Semantic versioning with one rule for API changes (Alex, 2026-11-06): a change that mirrors the Didit API is a minor version, including one that breaks typed code (a type change, a field that can now be null, a removed field, operation or enum value). The API already behaves that way for every caller, so an older SDK version is not safer, and a major would keep users on it. The release notes list such changes under "API changes that can break typed code".
- A major version is only for a change to the SDK's own design: renaming or removing an SDK method or resource the API still has, changing client configuration, errors, retries, pagination or webhook verification behaviour, or dropping a supported runtime version. An API change never needs a new Go
/vNmodule path. - Each language keeps its own version number: Node, PHP and Ruby moved to 2.x on 2026-11-05 under the earlier rule and continue from there.
- Releases are cut by release-please from conventional commits;
CHANGELOG.mdis generated and never edited by hand. - Release workflows publish with trusted publishing where the registry supports it (PyPI, npm with provenance, RubyGems and NuGet), Maven Central through the Central Portal with signed artifacts (token and signing key in the
maven-centralenvironment), Packagist through its GitHub hook, and Go module tags announced to proxy.golang.org. - 1.0.0 shipped on 2026-10-29 with Alex's approval, and the repositories are public.
- Every release after that publishes straight from the repository, with no person in the loop for API changes (Alex, 2026-11-07).
.github/workflows/bot-pull-requests.ymlstarts CI on the spec sync and release-please pull requests and turns on auto-merge with squash, so each merges once the requiredCI passedcheck passes. Merging the release pull request creates the vX.Y.Z tag and publishes, and the release workflow then waits until the registry serves the version, or fails. - A pull request opened with
GITHUB_TOKENgets itspull_requestCI run only after a maintainer approves it (the repositories are public), so that workflow startsci.ymlwithworkflow_dispatch. GitHub leaves the check runs of a manual run off the pull request, so that run'sreportjob sets its result as the commit statusCI passedon the head commit, which the pull request shows and the required check reads. A merge made by that auto-merge starts no push workflow, so thereportjob, andbot-pull-requests.ymlevery 30 minutes, start the release workflows onmainwhen the newest commit has no run of them. - A person decides, and the pull request gets the
needs-alexlabel instead of auto-merge, when a spec sync failed a check, when a pull request asks for a major version (a release pull request for a new major version, or a sync commit with!or aBREAKING CHANGE:footer), or when it carries commits from people. Thedo-not-mergelabel, or turning auto-merge off by hand, keeps any of these pull requests from merging. - A release created with
GITHUB_TOKENdoes not trigger other workflows, so a repository that publishes from a separaterelease.ymlhas release-please start it withworkflow_dispatchfor the new tag. No personal access token is stored.
- README: one-line pitch ("The official Didit library"), install, a quickstart (create a session, read the decision, verify a webhook), configuration, errors, pagination, webhooks, Authentication, links to docs.didit.me, supported runtimes, and versioning policy.
examples/: runnable examples for the main flows (KYC session, standalone ID verification, AML screening, transaction screening, Travel Rule, Authentication token verification) and webhook receivers for the popular frameworks.- Package metadata with description, keywords (identity verification, KYC, KYB, AML, liveness, transaction monitoring, Travel Rule, Didit), homepage
https://didit.me, docshttps://docs.didit.me, repository and license. - docs.didit.me gets a Server SDKs section and per-operation code samples in every language (OpenAPI
x-codeSamples), published together with the first release. - License MIT (
Copyright (c) 2026 Didit), like the client SDKs.SECURITY.mdpoints to GitHub private vulnerability reporting and support@didit.me.
Any change to a public API endpoint, parameter, request or response field, enum value, error, webhook payload or authentication flow in service-didit-verification or service-didit-auth must update the OpenAPI documents in mintlify-docs in the same change set.
The spec sync (3.1) then opens a pull request in every SDK repository; whoever changed the backend makes sure those pull requests merge, adding hand-written core changes (webhooks, Authentication) where the change needs them.
The rule lives in CLAUDE.md and AGENTS.md of the backends, of mintlify-docs, of every SDK repository, and of the Didit workspace.