Developers

Couri API

Base URL https://api.usecouri.com OpenAPI 3.1 document

Certify an email from your own systems, and let anybody check a certification. Every route here is live and answers today.

Certifying needs an API key, created in your dashboard at app.usecouri.com and shown once. Send it as a bearer token: Authorization: Bearer ck_live_... Keys carry the certify and verify scopes. Checking a certification needs no key at all.

Errors come back as JSON with an error code, for example {"error": "session_not_found"}, sometimes with a detail field. A bad or missing key always gives 401 {"error": "unauthorized"} with no further detail. Limits are per minute; responses carry RateLimit headers and 429 {"error": "rate_limited"} when you go over.

Certify an email

Four steps. Initiate with the message, the sender approves it in the Confrmo app, poll until it is approved, then complete to receive the certification and the footer to put in the email.

POST/api/v1/certify/initiateAPI key, certify scope30 requests a minute

Start a certification

Body (JSON)
senderUserIdstringThe sender, who must be an active member of your organisation.
payload.senderEmailstringMust match the sender.
payload.recipientsstring[]At least one.
payload.subjectstringOptional.
payload.bodystringOptional.
payload.attachmentHashesstring[]SHA-256 of each attachment, lowercase hex.
payload.actionstringsend, forward, reply or reply-all.
payload.priorCertificationRefsstring[]Verification IDs this message replies to or forwards.
curl -X POST https://api.usecouri.com/api/v1/certify/initiate \
  -H "Authorization: Bearer $COURI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "senderUserId": "usr_123",
    "payload": {
      "senderEmail": "alex@example.com",
      "recipients": ["sam@example.org"],
      "subject": "Completion statement",
      "body": "Please find the statement attached.",
      "attachmentHashes": [],
      "action": "send",
      "priorCertificationRefs": []
    }
  }'
{
  "sessionId": "...",
  "messageHashPrefix": "3fa91c",
  "comparisonValue": "...",
  "approval": { "sid": "...", "openUrl": "...", "vcode": "...", "expiresAt": "..." }
}
Errors
400senderUserId_required, invalid_payload
403sender_not_in_tenant, sender_not_active, sender_email_mismatch, insufficient_scope
503approval_unavailable

GET/api/v1/certify/{sessionId}/statusAPI key, certify scope30 requests a minute

Poll for the sender's approval

status is pending, approved, denied or expired.

curl https://api.usecouri.com/api/v1/certify/SESSION_ID/status \
  -H "Authorization: Bearer $COURI_API_KEY"
{
  "sessionId": "...",
  "status": "approved",
  "expiresAt": "..."
}
Errors
404session_not_found

POST/api/v1/certify/{sessionId}/completeAPI key, certify scope30 requests a minute

Issue the certification

If you send the payload again it is hashed and must match what was approved, or the call fails closed with message_hash_mismatch. Put certifiedFooterHtml at the end of the email you send.

curl -X POST https://api.usecouri.com/api/v1/certify/SESSION_ID/complete \
  -H "Authorization: Bearer $COURI_API_KEY"
{
  "verificationId": "K7QM-4XRT-9PWD",
  "vcodeUti": "...",
  "vcodeImageUrl": "...",
  "issuedAt": "...",
  "signature": "...",
  "signingKeyId": "...",
  "replyAddress": "...",
  "certifiedFooterHtml": "..."
}
Errors
409session_not_approved, session_expired, message_hash_mismatch, approval_already_consumed
403sender_not_linked_to_confrmo, approver_is_not_sender

POST/api/v1/certify/{sessionId}/cancelAPI key, certify scope30 requests a minute

Cancel a certification that has not been issued

curl -X POST https://api.usecouri.com/api/v1/certify/SESSION_ID/cancel \
  -H "Authorization: Bearer $COURI_API_KEY"
{
  "sessionId": "...",
  "status": "declined"
}
Errors
404session_not_found
409session_already_certified

Check a certification

Open to anybody, with no key. A Verification ID is three groups of four characters, such as K7QM-4XRT-9PWD.

GET/api/v1/verify/{verificationId}None300 requests a minute

Everything public about one certification

binding.strength is recorded, displayed or attested. ledger is null until the record is chained.

curl https://api.usecouri.com/api/v1/verify/K7QM-4XRT-9PWD
{
  "verificationId": "K7QM-4XRT-9PWD",
  "senderDomain": "example.com",
  "organisationName": "Example Ltd",
  "action": "send",
  "messageHash": "...",
  "recipientHashRoot": "...",
  "recipientScheme": "...",
  "recipientLeaves": null,
  "vcodeUti": "...",
  "issuedAt": "...",
  "status": "...",
  "binding": { "strength": "attested", "description": "..." },
  "ledger": { "blockNumber": 1042, "recordHash": "...", "previousHash": "...", "ledgerMode": "...", "status": "..." }
}
Errors
400invalid_verification_id
404not_found

GET/api/v1/verify/{verificationId}/chainNone300 requests a minute

Where the certification sits in the record chain

curl https://api.usecouri.com/api/v1/verify/K7QM-4XRT-9PWD/chain
{
  "block": { "blockNumber": 1042, "recordHash": "...", "previousHash": "...", "issuedAt": "..." },
  "next": null
}
Errors
404not_chained

Service

GET/api/v1/healthNone

Liveness check

curl https://api.usecouri.com/api/v1/health
{
  "status": "ok",
  "service": "api-gateway",
  "version": "0.1.0"
}