cheqd Studio (formerly known as Credential Service) enables users to consume cheqd's identity functionality, such as DIDs, Trust Registries, Status Lists, Credential Payments and DID-Linked Resources over REST API. This enables users to integrate cheqd's functionality into existing applications or create a full end-to-end trusted ecosystem from the ground up.
Get started by setting up your cheqd Studio account below:
We run hosted endpoints for this package (in case you don't want to run it yourself) which have Swagger / OpenAPI definition endpoints that list all of the APIs and how they work.
The Swagger API definition pages are:
The application allows configuring the following parameters using environment variables.
LOG_LEVEL: specifies log level, like 'trace', 'debug', 'info', 'warn' or 'error';
MAINNET_RPC_URL: RPC endpoint for cheqd mainnet (Default:https://rpc.cheqd.net:443).TESTNET_RPC_URL: RPC endpoint for cheqd testnet (Default:https://rpc.cheqd.network:443).RESOLVER_URL: API endpoint for a DID Resolver endpoint that supportsdid:cheqd(Default:https://resolver.cheqd.net/1.0/identifiers/).APPLICATION_BASE_URL: URL of the application (external domain name).CORS_ALLOWED_ORIGINS: CORS allowed origins used in the app (optional). (Default:APPLICATION_BASE_URL).
The application supports two modes in which keys are managed: either just storing them in-memory while a container is running, or persisting them in a PostgresSQL database with Veramo SDK. Using an external Postgres database allows for "custodian" mode where identity and cheqd/Cosmos keys can be offloaded by client applications to be stored in the database.
By default, ENABLE_EXTERNAL_DB is set to off/false. To enable external Veramo KMS database, set ENABLE_EXTERNAL_DB
to true, then define below environment variables in .env file:
EXTERNAL_DB_CONNECTION_URL: PostgreSQL database connection URL, e.g.postgres://<user>:<password>@<host>:<port>/<database>.EXTERNAL_DB_ENCRYPTION_KEY: Secret key used to encrypt the Veramo key-specific database tables. This adds a layer of protection by not storing the database in plaintext.EXTERNAL_DB_CERTIFICATE: Custom CA certificate required to connect to the database (optional).
By default, the application has API authentication disabled (which can be changed in configuration). If, however, you'd like to run the app with API authentication features, the following variables need to be configured.
We use a self-hosted version of LogTo, which supports OpenID Connect. Theoretically, these values could also be replaced with LogTo Cloud or any other OpenID Connect identity provider.
By default, ENABLE_AUTHENTICATION is set to off/false. To enable external Veramo KMS database, set
ENABLE_AUTHENTICATION to true, then define below environment variables in .env file:
- Endpoints
LOGTO_ENDPOINT: API endpoint for LogTo serverLOGTO_DEFAULT_RESOURCE_URL: Root of API resources in this application to be guarded. (Default:http://localhost:3000/on localhost.)LOGTO_MANAGEMENT_API: URL of management API for LogTo. This is typically static within self-hosted LogTo applications and is not meant to be a resolvable URL. (Default:https://default.logto.app/api)
- User-facing APIs
LOGTO_APP_ID: Application ID for the cheqd Studio application in LogTo. This can be set up as type "Traditional Web"LOGTO_APP_SECRET: Application secret associated with App ID above.
- Machine-to-machine backend APIs
LOGTO_M2M_APP_ID: Application ID for machine-to-machine application in LogTo. This is used for elevated management APIs within LogTo.LOGTO_M2M_APP_SECRET: Application secret
- Default role update using LogTo webhooks: LogTo supports
webhooks to fire of requests to an API when it detects certain actions/changes. If you want to automatically assign a
role to users, a webhook is recommended to be setup for firing off whenever there's a new account created, or a new
sign-in.
LOGTO_DEFAULT_ROLE_ID: LogTo Role ID for the default role to put new users into.LOGTO_WEBHOOK_SECRET: Webhook secret to authenticate incoming webhook requests from LogTo.
- Miscellaneous
COOKIE_SECRET: Secret for cookie encryption.API_KEY_EXPIRATION(optional): Expiration time for API keys in days. (Default 30 days)
This section describes bootstrapping things for newcomers accounts. If it's enabled cheqd Studio auto-populates some tokens on the testnet for making the process simpler.
ENABLE_ACCOUNT_TOPUP: Enable/disable such functionality (falseby default)FAUCET_URI: Faucet service API endpoint (Default:https://faucet-api.cheqd.network/credit)TESTNET_INITIAL_TOPUP_CHEQ: Amount of testnetCHEQa new account's testnet address is topped up to when it is bootstrapped (only ifENABLE_ACCOUNT_TOPUPistrue, and only when the testnet account is newly created, not on later calls), converted toncheqinternally. The top-up is debited from the customer's monthly faucet quota (FAUCET_MONTHLY_LIMIT_CHEQ); if the quota is exhausted it is skipped and account creation still succeeds. The old nameTESTNET_FAUCET_UPPER_CAP_CHEQis no longer read, because it was the faucet cap and would inflate this amount. (Default:TESTNET_MINIMUM_BALANCE, so 10,000 CHEQ)TESTNET_MINIMUM_BALANCE: Backward-compatible fallback for the initial top-up amount ifTESTNET_INITIAL_TOPUP_CHEQis not set. (Default: 10,000 CHEQ testnet tokens)FAUCET_MONTHLY_LIMIT_CHEQ: The mostCHEQa customer can request throughPOST /account/faucetacross all their addresses per calendar month (UTC). Usable with a Studio session or an API key. Requests that would exceed it return429with aRetry-Afterheader. The initial top-up at account creation counts towards it. (Default: 100,000 CHEQ)FAUCET_ADDRESS_CAP_CHEQ: The mostCHEQa single testnet address can hold beforePOST /account/faucetstops topping it up. (Default:FAUCET_MONTHLY_LIMIT_CHEQ, so there is one figure unless this is set) 6a.FAUCET_DEFAULT_REQUEST_CHEQ: The amount ofCHEQPOST /account/faucetsends when the request has noamount, so one call with only an address does not use the whole monthly quota. It is lowered to the room underFAUCET_ADDRESS_CAP_CHEQor the quota left if either is smaller. An explicitamountis not affected. (Default: 10,000 CHEQ)FAUCET_SUBSCRIPTION_CACHE_SECONDS: How longPOST /account/faucetcaches a customer's Stripe subscription status and plan, so API-key use does not call Stripe on every request. The stored subscription status is still checked on every request, so a cancellation takes effect immediately; a plan change can take this long to be seen.0disables the cache. (Default: 60)FAUCET_MIN_INTERVAL_SECONDS: Minimum gap between twoPOST /account/faucetrequests from the same customer, so a script cannot hammer the endpoint in a tight loop. A request made too soon returns429with aRetry-Afterheader and does not reach Stripe, the RPC node or the faucet.0disables it. Does not apply to the automatic top-up at account creation. (Default: 10)FAUCET_PENDING_TIMEOUT_SECONDS: How long a faucet quota reservation that has not been confirmed counts towards the quota before it is treated as abandoned (for example because the process died before calling the faucet). Keep it longer than the longest faucet call. A reservation whose faucet call threw (outcome unknown, so tokens may have been sent) always keeps counting. (Default: 600)FAUCET_BALANCE_SETTLE_SECONDS: A faucet transfer takes a few blocks to show up in an address's on-chain balance. For this many seconds, requests already made for the same address are subtracted from the room left underFAUCET_ADDRESS_CAP_CHEQ, so simultaneous requests cannot each be allowed the full room. (Default: 20)FAUCET_REQUEST_RETENTION_MONTHS: How many calendar months of faucet request history to keep in thefaucetRequesttable. Only the current month is needed to enforce the quota; older rows are kept for support and deleted after this. The cleanup runs inside normal faucet requests, at most once an hour per process (Studio has no scheduler).0keeps everything. To clean up by hand:DELETE FROM "faucetRequest" WHERE "createdAt" < date_trunc('month', now() AT TIME ZONE 'UTC') - interval '13 months';(Default: 13)FAUCET_REQUEST_TIMEOUT_SECONDS: How long to wait for the faucet service to answer before giving up. APOST /account/faucetthat times out returns504; the faucet may still have credited the account, so the quota used is kept. (Default: 30)FAUCET_API_KEY: API key for faucet service authenticationFAUCET_ACCESS_CLIENT_ID: Cloudflare Access client ID for accessing the Testnet Faucet APIFAUCET_ACCESS_CLIENT_SECRET: Cloudflare Access client secret for accessing the Testnet Faucet API
POST /account/faucet accepts three kinds of credential: a Studio user session (bearer token), an API key (x-api-key header) and a machine-to-machine token (bearer token plus a customer-id header selecting the customer). All three are checked against the same rule: the scopes they carry must include request:faucet:testnet. A user session and an API key get their scopes from the user's LogTo roles, so the scope comes with the Basic and Build plan roles; a machine-to-machine token carries its scopes in the token, so request:faucet:testnet must be granted to the M2M application's role in LogTo. The plan check, address ownership, quota and limits apply identically to all three.
The faucet quota, limits and handlers have two Postgres-backed test suites that are skipped unless FAUCET_TEST_DATABASE_URL points at a disposable database, so a plain npm run test:unit and CI are unaffected. Start a database with docker run -d --rm --name faucet-test-db -e POSTGRES_PASSWORD=pw -e POSTGRES_DB=studio -p 55432:5432 postgres:16, then:
- the ledger tests (jest):
FAUCET_TEST_DATABASE_URL=postgres://postgres:pw@localhost:55432/studio npm run test:unit -- faucet-request.service - the handler tests (Node's test runner, which needs Node 20 because the controller's dependencies do not load on newer versions):
FAUCET_TEST_DATABASE_URL=postgres://postgres:pw@localhost:55432/studio npx -y -p node@20 node --import tsx --import ./tests/integration/hooks/register.mjs --test tests/integration/faucet-handler.test.ts
Faucet decisions are logged as one JSON line each, with an event name, so a blocked or refunded request can be traced: faucet.reserved, faucet.completed, faucet.released (the faucet reported a failure, quota given back), faucet.unknown (the faucet call threw, quota kept), faucet.quota_exceeded, faucet.too_frequent, faucet.address_cap_exceeded, faucet.reservations_abandoned, faucet.bootstrap_topup_skipped, faucet.retention_deleted and the *_failed errors. Rejections are warnings and carry the customer, address, amount and the relevant figures (usedNcheq, limitNcheq, roomNcheq, retryAfterSeconds); the early checks that run before Stripe are marked "stage":"precheck". A customer's history is also in the faucetRequest table: SELECT "createdAt", address, "amountNcheq", status FROM "faucetRequest" WHERE "customerId" = '<id>' ORDER BY "createdAt" DESC;.
Studio exposes POST /account/faucet for authenticated Studio users on Basic-or-higher subscriptions. The endpoint accepts optional address and amount fields; amount is expressed in CHEQ. If address is omitted, Studio uses the customer's testnet payment account. If amount is omitted, Studio sends FAUCET_DEFAULT_REQUEST_CHEQ (10,000 CHEQ by default), or less if the room under FAUCET_ADDRESS_CAP_CHEQ or the monthly quota left is lower; pass amount to request more. Requests that exceed the address cap return a requestMore.mailtoHref for contacting product@cheqd.io.
GET /account/balances (read:account scope) returns the on-chain balance of the authenticated customer's mainnet and testnet payment accounts in ncheq, CHEQ and USD. The CHEQ/USD rate is fetched from CoinGecko and cached in-process. The endpoint degrades gracefully: if the rate lookup or a network's RPC endpoint is unavailable it still returns 200, with the affected usd / rate / balance fields set to null.
COINGECKO_API_URL: CoinGecko API base URL (Default:https://api.coingecko.com/api/v3). Set to apro-api.coingecko.comURL to use a Pro key.COINGECKO_TOKEN_ID: CoinGecko coin id for CHEQ (Default:cheqd-network).COINGECKO_API_KEY(optional): CoinGecko API key. Sent asx-cg-demo-api-key, orx-cg-pro-api-keywhenCOINGECKO_API_URLpoints at the Pro API.CHEQ_USD_RATE_CACHE_TTL(optional): seconds to cache the CHEQ/USD rate for (Default:300).
The application supports Stripe integration for payment processing.
STRIPE_ENABLED- Enable/disable Stripe integration (falseby default)STRIPE_SECRET_KEY- Secret key for Stripe API. Please, keep it secret on deployingSTRIPE_PUBLISHABLE_KEY- Publishable key for Stripe API.STRIPE_WEBHOOK_SECRET- Secret for Stripe Webhook.STRIPE_BUILD_PLAN_ID- Subscription product ID of the Build planSTRIPE_BASIC_PLAN_ID- Subscription product ID of the Basic plan. This replaces the former Explorer/Test plan.STRIPE_TEST_PLAN_ID- Backward-compatible fallback product ID for the former Test/Explorer plan. Keep for one release while migrating toSTRIPE_BASIC_PLAN_ID.
When a new Studio customer is created with Stripe enabled, Studio creates a Stripe customer and starts the Basic plan as a 30-day trial subscription. This makes the customer immediately trialing for subscription checks without requiring a payment method at signup.
The app supports 3rd party connectors for credential storage and delivery.
The app's Verida Network connector can be enabled to deliver generated credentials to Verida Wallet.
By default, ENABLE_VERIDA_CONNECTOR is set to off/false. To enable external Veramo KMS database, set
ENABLE_VERIDA_CONNECTOR to true, then define below environment variables in .env file:
VERIDA_PRIVATE_KEY: Secret key for Verida Network API.POLYGON_PRIVATE_KEY: Secret key for Polygon Network.
If you want to run the application without any external databases or dependent services, we provide a Docker Compose file to spin up a standalone service.
docker compose -f docker/no-external-db/docker-compose-no-db.yml up --detachThis standalone service uses an in-memory database with no persistence, and therefore is recommended only if you're managing key/secret storage separately.
The no-db.env file in the same folder contains all the environment variables
necessary to configure the service. (See section Configuration above.)
Construct the postgres URL and configure the env variables mentioned above.
Spinning up a Docker container from the pre-built studio Docker image on Github is as simple as the command below:
Configure the environment variables in the postgres.env file:
POSTGRES_USER: Username for Postgres databasePOSTGRES_PASSWORD: Password for Postgres databasePOSTGRES_MULTIPLE_DATABASES: Database names for multiple databases in the same cluster, e.g.:"app,logto". This sets up multiple databases in the same cluster, which can be used independently for External Veramo KMS or LogTo service.
Then, make the Postgres initialisation scripts executable:
chmod +x docker/with-external-db/pg-init-scripts/create-multiple-postgresql-databases.shConfigure the environment variables in the logto.env file with the settings
described in section above.
Then, run the LogTo service to configure the LogTo application API resources, applications, sign-in experiences, roles etc using Docker Compose:
docker compose -f docker/with-external-db/docker-compose-with-db.yml --profile logto up --detachConfiguring LogTo is outside the scope of this guide, and we recommend reading LogTo documentation to familiarise yourself.
Configure the environment variables in the with-db.env file with the settings
described in section above. Depending on whether you are using external Veramo KMS only, LogTo only, or both you will
need to have previously provisioned these services as there are environment variables in this file that originate from
Postgres/LogTo.
Then, start the service using Docker Compose:
docker compose -f docker/with-external-db/docker-compose-with-db.yml up --detachWhen upgrading either the external Veramo KMS or LogTo, you might need to run migrations for the underlying databases.
You can run just the migration scripts using Docker Compose profiles defined in the Compose file.
For example, to run cheqd Studio app migrations on an existing Postgres database (for external Veramo KMS):
docker compose -f docker/with-external-db/docker-compose-with-db.yml --profile app-setup up --detachOr to run LogTo migrations on an existing Postgres database:
docker compose -f docker/with-external-db/docker-compose-with-db.yml --profile logto-setup up --detachTo build your own image using Docker, use the Dockerfile provided.
docker build --file docker/Dockerfile --target runner . --tag studio:localIf you notice anything not behaving how you expected, or would like to make a suggestion / request for a new feature, please create a new issue and let us know.
Our Discord server is our primary chat channel for the open-source community, software developers, and node operators.
Please reach out to us there for discussions, help, and feedback on the project.