Credential Operations
Endpoints for issuing and revoking verifiable credentials. All support prepare/submit flow and all require an API key (X-ACTA-Key). Issue Credential (POST /contracts/vc/issue) and Batch Issue (POST /contracts/vc/batch-issue) additionally require that owner matches the wallet bound to your API key (admin-role keys are exempt).
Issue Credential
POST /contracts/vc/issue
Issues a VC: stores the payload in the owner's vault and writes issuance status = valid. Requires API key.
Headers:
X-ACTA-Key: your_api_key_hereRequest Body (Prepare):
{
"owner": "G...",
"vcId": "credential-123",
"vcData": "{\"@context\":[\"https://www.w3.org/ns/credentials/v2\",\"https://www.w3.org/ns/credentials/examples/v2\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":{\"id\":\"did:stellar:...\",\"name\":\"John Doe\"}}",
"issuer": "G...",
"issuerDid": "did:stellar:...",
"userSalt": "00...00",
"sourcePublicKey": "G..."
}The credential holder is identified by credentialSubject.id (a DID) inside vcData. There is no separate holder field.
Request Body (Submit):
{
"signedXdr": "AAAA..."
}Response (Prepare):
{
"xdr": "AAAA...",
"network": "Test SDF Network ; September 2015"
}Response (Submit):
{
"tx_id": "abc123..."
}Example:
# Prepare
curl -X POST https://api.testnet.acta.build/contracts/vc/issue \
-H "X-ACTA-Key: your_key" \
-H "Content-Type: application/json" \
-d '{
"owner": "G...",
"vcId": "credential-123",
"vcData": "{\"@context\":[\"https://www.w3.org/ns/credentials/v2\",\"https://www.w3.org/ns/credentials/examples/v2\"],\"type\":[\"VerifiableCredential\"]}",
"issuer": "G...",
"issuerDid": "did:stellar:...",
"sourcePublicKey": "G..."
}'
# Submit (after signing)
curl -X POST https://api.testnet.acta.build/contracts/vc/issue \
-H "X-ACTA-Key: your_key" \
-H "Content-Type: application/json" \
-d '{
"signedXdr": "AAAA..."
}'Batch Issue
POST /contracts/vc/batch-issue
Issues several VCs into the same owner's vault in a single transaction. Each entry carries only vcId and vcData; the holder of each credential is credentialSubject.id inside its own vcData (there is no holder field). Requires API key.
Limits: 1 to 5 credentials per batch (MAX_BATCH_SIZE = 5); vcId up to 64 characters; vcData up to 10,000 characters. Exceeding them returns 400 batch_too_large, 400 vcs[i].vcId_too_long, or 400 vcs[i].vcData_too_long.
Request Body (Prepare):
{
"owner": "G...",
"issuer": "G...",
"issuerDid": "did:stellar:...",
"userSalt": "00...00",
"sourcePublicKey": "G...",
"vcs": [
{
"vcId": "credential-1",
"vcData": "{\"@context\":[\"https://www.w3.org/ns/credentials/v2\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":{\"id\":\"did:stellar:...\"}}"
},
{
"vcId": "credential-2",
"vcData": "{\"@context\":[\"https://www.w3.org/ns/credentials/v2\"],\"type\":[\"VerifiableCredential\"],\"credentialSubject\":{\"id\":\"did:stellar:...\"}}"
}
]
}Request Body (Submit):
{
"signedXdr": "AAAA..."
}Responses: Prepare returns { xdr, network }; Submit returns { tx_id }.
Revoke Credential
POST /contracts/vc/revoke
Revokes a VC by ID in a specific owner's vault. Requires an API key. The transaction must be signed by the vault owner (the contract enforces owner.require_auth()).
Request Body (Prepare):
{
"owner": "G...",
"vcId": "credential-123",
"date": "2024-01-15T00:00:00.000Z",
"userSalt": "00...00",
"sourcePublicKey": "G..."
}Request Body (Submit):
{
"signedXdr": "AAAA..."
}Response (Prepare):
{
"xdr": "AAAA...",
"network": "Test SDF Network ; September 2015"
}Response (Submit):
{
"tx_id": "abc123..."
}Issuer DID requirement
The issuerDid must be a registered, resolvable `did:stellar`. Bare wallet addresses and did:pkh values are no longer accepted. The API enforces a controller-to-DID binding: the DID's on-chain controller must equal the signing issuer. If they differ, the request fails with:
{
"error": "issuerDid_controller_mismatch",
"message": "The issuer DID controller does not match the signing issuer"
}Fees
Issuance charges an on-chain fee computed by the vault via the factory's quote_fee and paid by the issuer (mainnet: 1 USDC per credential; testnet: 5 XLM). The API does not accept a fee override: there is a single standard fee plus an optional per-issuer custom fee, both resolved on-chain.
Request Body
Issue Credential
- owner (required): Vault owner address (G...)
- vcId (required): Credential identifier (max 64 characters)
- vcData (required): Credential data payload (JSON string, max 10,000 characters). Must include
@contextwith at least"https://www.w3.org/ns/credentials/v2", andcredentialSubject.id(the holder DID). The API encryptsvcData(AES-256-GCM) before it is stored on-chain - issuer (required): Issuer address (G...)
- issuerDid (required): Registered, resolvable
did:stellarwhose on-chain controller equalsissuer - userSalt (optional): 32-byte salt selecting the owner's vault; defaults to 32 zero bytes
- sourcePublicKey (required): Transaction source that will sign (must be the issuer)
Batch Issue
- owner (required): Vault owner address (G...)
- issuer (required): Issuer address (G...)
- issuerDid (required): Registered, resolvable
did:stellarwhose on-chain controller equalsissuer - vcs (required): Array of
{ vcId, vcData }. EachvcDatacarries its owncredentialSubject.idholder DID - userSalt (optional): 32-byte salt selecting the owner's vault
- sourcePublicKey (required): Transaction source that will sign (must be the issuer)
Revoke Credential
- owner (required): Vault owner address (G...) whose vault holds the credential
- vcId (required): Credential identifier
- date (optional): ISO-8601 timestamp (default: now)
- userSalt (optional): 32-byte salt selecting the owner's vault
- sourcePublicKey (required): Transaction source that will sign (must be the VC owner or contract admin)
Prepare/Submit Flow
- Prepare: Send request with operation parameters (no
signedXdr) - Sign: Sign the returned
xdrwith your Stellar wallet using thenetworkpassphrase - Submit: Send request with
signedXdrto execute
Note: issue stores the credential in the vault and marks it as valid in a single transaction.