Skip to content
Developers
Menu

Guides

Issue a credential

Issue a digital credential from your system of record, retry safely, and confirm delivery with the list endpoint and webhooks.

Updated

On this page

This guide issues one digital credential through the API when a learner completes a course in your own system.

Before you begin

  • An API key with the credentials:write scope. Add credentials:read if you also want to confirm issuance by listing credentials.
  • At least one allowed email domain in your organisation's settings. Recipients' work_email must use one of them.
  • Optionally, a certificate template saved in Templates. Without one, your default template is used.

Every successful request issues a real credential and emails the recipient. There is no test mode. Setting is_historical to true sends no claim email, but it still creates a real archival credential that counts towards your plan.

1. Build the request

Choose a stable Idempotency-Key from your own data, such as your course and learner IDs, and always send issue_date. Together they make retries safe.

async function issueCredential({ learner, course, completedOn }) {
  const response = await fetch("https://app.arrota.com/api/v1/credentials", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ARROTA_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `course-${course.id}-learner-${learner.id}`,
    },
    body: JSON.stringify({
      recipient_name: learner.fullName,
      work_email: learner.email,
      document_type: "Training Record",
      issue_date: completedOn, // YYYY-MM-DD
      credential_id: `${course.code}-${learner.id}`,
    }),
  });
  return { status: response.status, body: await response.json() };
}

2. Handle the response

  • 202: issued. Store document_id and record_id against the learner.
  • 200 with "status": "already_issued": this exact issuance happened before, for example on an earlier retry. Store the returned document_id; nothing new was created.
  • 400: fix the request. fields lists invalid inputs, and code names business rules such as DOMAIN_NOT_ALLOWED.
  • 403 with SEAT_CAP: your plan's recipient limit is reached.
  • 429, 500, 503: retry the same request later. Because the body and key are unchanged, a retry cannot create a duplicate.

3. Confirm and follow the credential

  • List recent credentials with GET /api/v1/credentials?limit=1 to see it.
  • Subscribe to webhooks: credential.issued confirms issuance, credential.claimed tells you the recipient added it to their Vault, and credential.revoked tells you if it is withdrawn.

Employment documents and background checks cannot be issued this way. See Credentials for the full field reference.

Using Arrota rather than building on it? The Help Center covers product workflows.