Skip to Content
How-To GuidesQuick Start: Issuer Registry

Quick Start: Issuer Registry

The Issuer Registry runs a gRPC server on port 50053 for inter-service communication with Core Engine. See Issuer Registry for the full RPC reference.

Start the service

Docker Compose:

docker-compose up -d issuer-registry docker-compose logs issuer-registry | grep "gRPC"

Standalone:

docker run -d --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:18 docker run -d --name redis -p 6379:6379 redis:8.6-alpine ./gradlew :backends:issuer-registry:bootRun # Netty started on port 8083 # gRPC Server started, listening on port 50053

Install grpcurl

go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

Exercise every RPC

# List services grpcurl -plaintext localhost:50053 list # com.attestpro.registry.IssuerRegistryService grpcurl -plaintext localhost:50053 describe com.attestpro.registry.IssuerRegistryService

RegisterIssuer

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/RegisterIssuer <<< '{ "name": "National Board of Education", "website_url": "https://nbe.gov", "public_key": "dGVzdF9wdWJsaWNfa2V5XzMyX2J5dGVzXzEyMzQ1Njc4OQ==", "key_algorithm": "Ed25519", "contact_email": "admin@nbe.gov", "country_code": "US" }' # Response — status is PENDING_APPROVAL, not ACTIVE: # { "issuerId": "550e8400-...", "issuerDid": "did:key:z...", "status": "PENDING_APPROVAL", # "signingMode": "CUSTODIAL" }

This example omits byok (defaults to false), so it registers a CUSTODIAL issuer — Attest ID generates its own keypair server-side, and the submitted public_key is ignored entirely. Only "byok": true uses the submitted public_key as the issuer’s real verification key.

GetIssuer

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/GetIssuer <<< '{ "issuerDid": "did:key:zdGVzdF9wdWJsaWNfa2V5" }'

GetDIDDocument

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/GetDIDDocument <<< '{ "issuerDid": "did:key:zdGVzdF9wdWJsaWNfa2V5" }'

RotateKey

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/RotateKey <<< '{ "issuerDid": "did:key:zdGVzdF9wdWJsaWNfa2V5", "newPublicKey": "bmV3X3B1YmxpY19rZXlfMzJfYnl0ZXNfOTg3NjU0MzIx", "oldKeySignature": "optional_signature" }'

UpdateIssuerStatus

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/UpdateIssuerStatus <<< '{ "issuerDid": "did:key:zdGVzdF9wdWJsaWNfa2V5", "newStatus": "SUSPENDED", "reason": "Administrative suspension", "adminSignature": "optional_signature" }'

adminSignature is accepted by the request shape but never validated by this RPC; reason is audited by the caller (Core Engine’s RegistryOrchestrator), not persisted here. The realistic path to approve a PENDING_APPROVAL issuer is Core Engine’s REST endpoint, not this RPC directly: POST /api/v1/issuers/{issuer_did}/approve (or /reject).

SignPayload

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/SignPayload <<< '{ "issuerDid": "did:key:zdGVzdF9wdWJsaWNfa2V5", "payload": "dGVzdCBwYXlsb2Fk" }'

Only works for a CUSTODIAL issuer (one with a row in authority.signing_keys) — a BYOK issuer returns NOT_FOUND.

ListIssuers

grpcurl -plaintext -d @ localhost:50053 \ com.attestpro.registry.IssuerRegistryService/ListIssuers <<< '{ "statusFilter": "PENDING_APPROVAL", "page": 0, "pageSize": 20 }'

Backs the admin console’s pending-issuers queue.

Build and test

./gradlew :backends:issuer-registry:build -x test ./gradlew :backends:issuer-registry:test ./gradlew :backends:issuer-registry:spotlessApply ./gradlew :backends:issuer-registry:check

Configuration

# backends/issuer-registry/src/main/resources/application.yml server: port: 8083 grpc: server: port: 50053 enable-keep-alive: true keep-alive-time: 30s keep-alive-timeout: 10s permit-keep-alive-without-calls: true max-inbound-message-size: 10485760 spring: datasource: url: jdbc:postgresql://localhost:5432/attest_pro username: postgres password: postgres redis: host: localhost port: 6379

The REST path (via Core Engine, not this service)

Issuer registration is a Core Engine REST endpoint (proxied through API Gateway on 8000), not an Issuer Registry REST endpoint on 8083:

curl -X POST http://localhost:8080/api/v1/issuer/register \ -H "Content-Type: application/json" \ -d '{ "name": "Test Issuer", "websiteUrl": "https://test.org", "contactEmail": "admin@test.org", "countryCode": "US", "byok": false }' # Response status will be "PENDING_APPROVAL" curl http://localhost:8080/api/v1/issuers/did:key:test curl -X POST http://localhost:8080/api/v1/issuers/did:key:test/approve \ -H "Authorization: Bearer <admin-jwt>"

Troubleshooting

SymptomFix
gRPC server not startinglsof -i :50053; confirm Postgres/Redis are up
Connection timeoutnc -zv localhost 50053
Proto compilation errors./gradlew :backends:shared:generateProto then ./gradlew :backends:issuer-registry:clean :backends:issuer-registry:compileJava
Cache issuesredis-cli FLUSHDB

Database

Flyway auto-applies on startup. Tables (all in the authority schema):

  • authority.issuers — status (PENDING_APPROVAL by default), signing mode
  • authority.did_registry — key version history
  • authority.signing_keys — AES-256-GCM-encrypted custodied private keys (CUSTODIAL only)
  • authority.issuer_sso_config — OIDC/SAML 2.0 SSO configuration per issuer