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 50053Install grpcurl
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latestExercise every RPC
# List services
grpcurl -plaintext localhost:50053 list
# com.attestpro.registry.IssuerRegistryService
grpcurl -plaintext localhost:50053 describe com.attestpro.registry.IssuerRegistryServiceRegisterIssuer
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:checkConfiguration
# 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: 6379The 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
| Symptom | Fix |
|---|---|
| gRPC server not starting | lsof -i :50053; confirm Postgres/Redis are up |
| Connection timeout | nc -zv localhost 50053 |
| Proto compilation errors | ./gradlew :backends:shared:generateProto then ./gradlew :backends:issuer-registry:clean :backends:issuer-registry:compileJava |
| Cache issues | redis-cli FLUSHDB |
Database
Flyway auto-applies on startup. Tables (all in the authority schema):
authority.issuers— status (PENDING_APPROVALby default), signing modeauthority.did_registry— key version historyauthority.signing_keys— AES-256-GCM-encrypted custodied private keys (CUSTODIAL only)authority.issuer_sso_config— OIDC/SAML 2.0 SSO configuration per issuer