Competency data
An issuance template (achievement) carries three kinds of competency data: the criteria, the skill tags, and the description engraved on the certificate (imprintDescription). This guide explains how that data appears in the API and how it is frozen at issuance time.
One principle: there is exactly one frozen record
Section titled “One principle: there is exactly one frozen record”In Kolleges the only record that never changes is the signed credential (VC-JWT). A template’s criteria, tags and description are live data you can edit at any time; at the moment of issuance, the values as they are then are signed into the credential. Editing the template afterwards does not move credentials that were already issued.
So the values the template API returns are both “what is stored now” and “what the next credential will carry”. To keep those two identical, template reads are computed with the same functions the issuer uses when it builds a credential.
Reading a template (Open Badges 3.0 shape)
Section titled “Reading a template (Open Badges 3.0 shape)”The detail from GET /v2/clubs/{clubDomain}/achievements/{id} carries these fields.
| Field | Meaning | Where it lands in the credential |
|---|---|---|
tag |
The normalised skill keyword set (trimmed, empties dropped, de-duplicated) | achievement.tag[] |
alignment[] |
The same set expressed as /skills/{tag} alignments |
achievement.alignment[] |
achievementType |
The internal type mapped to an OB3 term (e.g. completion → CertificateOfCompletion) |
achievement.achievementType |
criteria.narrative |
The criteria as one numbered sentence | achievement.criteria.narrative |
criteria.requirements[] |
Every requirement, in (order, id) order |
The numbering of the narrative |
coIssuers[] |
Every co-issuer, representative first | x-partnerOrganizations |
program |
The program { name, url }, only when public |
Not engraved (web guidance only) |
imprintDescription · imprintDescriptionEn |
The description engraved on the certificate image | The certificate image |
issuedCount |
Number of completed issuances | Not engraved |
alignment[], achievementType and criteria.narrative are read-only derived values. They do not exist in the write API; they are computed from the stored tags, type and requirements with the issuer’s own rules. Do not recompute them, use what you read.
List items (GET …/achievements) also carry tag, alignment, achievementType and issuedCount.
Writing a template: what cannot be engraved is not accepted
Section titled “Writing a template: what cannot be engraved is not accepted”The body of POST …/achievements and PATCH …/achievements/{id} has the same shape as the read. Every validation rule comes from a single question: can the issuer engrave this value?
descriptionis required. Open Badges 3.0 requiresachievement.description, and the issuer refuses to sign when it is empty. PATCH cannot clear it either.tagitems are trimmed and de-duplicated on save. Empty items and items containing a comma are rejected with 422 (errors[].messageistag-contains-comma). At most 30 items in the stored set, 50 characters each.nameEn,descriptionEnandimprintDescriptionEnaccept Latin script only. Mixed-in Hangul is rejected with 422 (en-text-not-latin), so that the English slots never hold a value that could not be engraved. An empty string is stored as “none”.criteria.requirements[]uses array position asorder; clients do not sendorder. Each item needs adescriptionor aurl(both empty → 422requirement-empty).typeis one of 16 codes.- Sending
criteria.requirementson PATCH replaces them in full. Delete then insert in one transaction, so requirementids change on every replace. An empty array deletes them all. programis an object; PATCH merges only the keys you send.- If you send
descriptionwithoutimprintDescription, the engraved description follows the new description only when it was a mirror of the old one. A separately written engraved description is left alone.
// POST /v2/clubs/{clubDomain}/achievements{ "name": "Frontend Bootcamp Certificate", "type": "completion", "description": "Issued to graduates who completed the 12-week course and presented a final project.", "tag": ["React", "TypeScript", "Teamwork"], "nameEn": "Frontend Bootcamp Certificate", "criteria": { "requirements": [ { "type": "course_complete", "description": "Complete the 12-week curriculum" }, { "type": "presentation_publication", "description": "Present the final project", "url": "https://acme.example/demo-day" } ] }, "program": { "name": "Frontend Bootcamp, cohort 5", "url": "https://acme.example/bootcamp", "isPublic": true }}The response is the detail right after saving, and its criteria.narrative is engraved verbatim into the next credential.
1. Complete the 12-week curriculum2. Present the final project (https://acme.example/demo-day)Order is part of the contract
Section titled “Order is part of the contract”Criteria are ordered (order ASC, id ASC) wherever you read them: the numbering of the narrative, the detail’s criteria.requirements[], and the issuance detail’s form.requirements[] all use the same order. Items that share an order are settled by id, so the order never wobbles.
If you are on v1 (/v1)
Section titled “If you are on v1 (/v1)”v1 only adds keys. Existing responses are unchanged, and the following were added (/docs v1.2).
- Issuance GET:
credential { id, shareUrl, verifyUrl } | null,evidences[].isPublic,form.requirements[].id,form.nameEn,form.descriptionEn - Template GET:
institutions[].role,institutions[].isVerified,requirements[].id,nameEn,descriptionEn - Bulk issue response:
issued[],failed[] - Requirement
typeinput: 8 codes became 16
On v1 the criteria are a requirements[] array and you send order yourself. New product features arrive on /v2 first.