API
Credentials
API reference for listing and issuing digital credentials with GET and POST /api/v1/credentials.
Updated
On this page
List credentials
GET/api/v1/credentials
Returns the digital credentials your organisation has issued, newest first. Requires the credentials:read scope.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Page size, 1 to 100. Default 50. |
cursor | string | The next_cursor from the previous page. See Pagination. |
status | string | active, revoked or expired. |
record_type | string | Exact document type, such as Certification. 1 to 100 characters. |
issued_after | date | YYYY-MM-DD. Credentials whose own issue date is on or after this day. Credentials with no known issue date are excluded. |
issued_before | date | YYYY-MM-DD. Credentials whose own issue date is on or before this day. Credentials with no known issue date are excluded. |
created_after | date | YYYY-MM-DD or an ISO timestamp. Credentials added to Arrota on or after this time. |
created_before | date | YYYY-MM-DD or an ISO timestamp. Credentials added to Arrota on or before this time. |
Example request
curl "https://app.arrota.com/api/v1/credentials?limit=10&status=active" \
-H "Authorization: Bearer $ARROTA_API_KEY"const url = new URL("https://app.arrota.com/api/v1/credentials");
url.searchParams.set("limit", "10");
url.searchParams.set("status", "active");
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.ARROTA_API_KEY}` },
});
const { data, next_cursor } = await response.json();Response
{
"data": [
{
"id": "5b6f0c2a-1d3e-4f5a-9b7c-8d9e0f1a2b3c",
"type": "Certification",
"status": "active",
"recipient_name": "Alex Rivera",
"issued_on": "2026-09-18",
"created_at": "2026-09-18T10:42:11.402Z",
"expires_on": "2029-09-18"
}
],
"next_cursor": null
}| Field | Description |
|---|---|
id | The credential's document_id. |
type | The document type it was issued as. |
status | revoked if revoked; expired if its expiry date has passed; otherwise active. |
recipient_name | The recipient's name, or null. |
issued_on | The date the credential was issued, as recorded by the issuer. For historical imports this is the original issue date, not the import date. null when the source gave no issue date. |
created_at | When the credential was added to Arrota (ISO timestamp, UTC). For historical imports this is the import time. |
expires_on | Expiry date, or null for none. |
next_cursor | Cursor for the next page, or null on the last page. |
The issued_after and issued_before filters use issued_on; created_after and created_before use created_at. Results are always ordered by created_at, newest first, so pagination is stable.
Changed in September 2026: issued_on and the issued_* filters previously used the Arrota creation time. They now use the credential's own issue date, and created_at carries the creation time.
Errors
400 for an invalid limit, status, record_type, date or cursor; 401; 403 for a missing scope or a plan without API access; 429. See Errors.
Issue a credential
POST/api/v1/credentials
Issues one digital credential from a certificate template and delivers it to the recipient. Requires the credentials:write scope. This creates a real credential; there is no test mode.
Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer followed by your API key. |
Content-Type | Yes | application/json |
Idempotency-Key | No | Your own key for this issuance. See Idempotency. |
Body
| Field | Type | Required | Description |
|---|---|---|---|
recipient_name | string | Yes | The person the credential is for. Up to 120 characters. |
work_email | string | Yes | Where the credential is delivered. Must use one of your organisation's allowed email domains. |
document_type | string | Yes | A document type such as Certification, Training Record, Degree, Diploma, Transcript, Badge, Award or Participation Certificate, or a custom type of 3 to 60 characters. |
issue_date | date | No | YYYY-MM-DD. Defaults to today (UTC). |
expiry_date | date | No | YYYY-MM-DD, not before issue_date. Omit for no expiry. |
template_id | string | No | Base design: classic-university (default), modern-tech or minimal-corporate. |
certificate_template_id | string | No | The ID of a template your organisation saved in Templates. It must be for the same document type. Without it, your default template is used. |
credential_id | string | No | Your own reference number. Up to 120 characters. |
custom_fields | object | No | Values for your organisation's custom fields, keyed by field key. |
is_historical | boolean | No | true creates an archival credential and sends no claim email. |
Employment documents (offer, appointment, promotion, experience and relieving letters, and employment verification) and background checks cannot be issued from a template. They must be evidenced by the original document, which you issue in the dashboard; the API refuses them with TEMPLATE_NOT_ALLOWED.
Example request
curl -X POST "https://app.arrota.com/api/v1/credentials" \
-H "Authorization: Bearer $ARROTA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: course-4471-alex-rivera" \
-d '{
"recipient_name": "Alex Rivera",
"work_email": "alex.rivera@example.com",
"document_type": "Certification",
"issue_date": "2026-09-18"
}'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-4471-alex-rivera",
},
body: JSON.stringify({
recipient_name: "Alex Rivera",
work_email: "alex.rivera@example.com",
document_type: "Certification",
issue_date: "2026-09-18",
}),
});Responses
202 Accepted: the credential was issued.
{
"ok": true,
"document_id": "5b6f0c2a-1d3e-4f5a-9b7c-8d9e0f1a2b3c",
"record_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
"verification_status": "Issuer Issued",
"email_queued": true,
"check": "/api/v1/credentials?limit=1"
}email_queued is false when no email was sent, for example for a historical import.
200 OK: an identical issuance already exists, so nothing new was created.
{ "ok": true, "document_id": "5b6f0c2a-1d3e-4f5a-9b7c-8d9e0f1a2b3c", "status": "already_issued" }Errors
| Status | code | When |
|---|---|---|
| 400 | none, with fields | A body field is missing or invalid. |
| 400 | SELF_ATTESTATION_BLOCKED | The recipient is one of your organisation's own administrators or team members. |
| 400 | DOMAIN_NOT_ALLOWED | work_email is outside your allowed domains. |
| 400 | none | Your organisation has not added an email domain yet (Settings, Domains). |
| 400 | TEMPLATE_NOT_ALLOWED | The document type must be issued from an original document. |
| 400 | TEMPLATE_TYPE_MISMATCH | certificate_template_id belongs to a different document type. |
| 403 | SEAT_CAP | Your plan's recipient limit is reached. |
| 403 | none | Missing scope, no API access on your plan, or the organisation cannot issue right now. |
See Errors for 401, 429 and server errors.
Related webhook events
credential.issued fires for every issued credential, credential.claimed when the holder claims it and credential.revoked if it is revoked. See Webhook events.