--- name: iblai-api-credential description: Manage an ibl.ai organization's digital credentials via the platform API — credential CRUD, user/group assignments, assertions, course-scoped credential import/export, external provider config + mapping (Accredible), and issuance analytics. Use when issuing, assigning, importing, or reporting on digital credentials and certificates. metadata: kind: api --- # iblai-api-credential Manage an organization's **digital credentials** from the API: create and edit credentials, assign them to users and groups, read the resulting assertions (issued credentials), import/export course-scoped credentials between platforms, wire up external issuing providers (e.g. Accredible) via provider config and per-credential external mappings, and pull issuance analytics over time. Use when issuing, assigning, importing, or reporting on digital credentials and certificates. ## Auth & conventions - **Base URL:** `https://api.iblai.app/dm` — these are Data Manager (DM) endpoints, so the **`/dm` prefix is required**; the `/api/credentials/…` paths below are appended to it (e.g. `https://api.iblai.app/dm/api/credentials/…`). - **Header:** `Authorization: Api-Token $IBLAI_API_KEY` on every request. - **Path vars:** `{org}` = `$IBLAI_ORG` (the credentials API path segment is `platform_key`; it resolves against `Platform.key`, with `Platform.org` as a fallback; on the wire it is also the `platform_org` query param). The user segment accepts **either** a numeric `{user_id}` **or** a `{username}` — both route shapes exist (`…/users//…` and `…/users//…`), so `$IBLAI_USERNAME` works directly in the path. For provider-config/mapping paths that user segment is the admin making the request (use `$IBLAI_USERNAME`). `{course_id}` = an Open edX course id such as `course-v1:org+course+run`. - **Most paths are user-scoped** under `/api/credentials/orgs/{org}/users/{user}/…` (where `{user}` is a numeric id or username). Below, that prefix is written as **`…/`** for brevity. The course import/export (`exim`) endpoints, the public assertion lookup, and the public provider list are platform-agnostic and live directly under `/api/credentials/…` with **no** org/user segment. - **Ids:** id-taking detail endpoints (credentials, assertions, assignments) look up **by `entity_id`** (the UUID string) only — numeric database ids are not accepted. `{id}` below means the `entity_id`. - **Responses** are wrapped: `{"status": {"success": bool, "description": str}, "result": {…}}`. Paged list `result` objects carry `count`, `next`, `previous`, and either `results` or `data`. - DELETE / destructive / outward-facing (assign, issue, import/export) calls — **confirm with the user first.** - Not connected yet? Run **`/iblai-api-login`** first to populate `IBLAI_ORG`, `IBLAI_USERNAME`, and `IBLAI_API_KEY`. ## Pagination, ordering & search (list endpoints) - `page` (default `1`), `page_size` (default `10`, max `100`; provider/mapping lists default `50`, max `1000`). - `search` — case-insensitive match across the relevant fields (username, email, full name, credential name/description, group name, platform name, course/program name, assigned-by username, depending on the endpoint). - Results are ordered newest-first (by creation / assignment date). For department admins, results are automatically filtered to their department scope. ## Reads ### Credentials — `…/` - **GET** `…/` — list credentials for the org (only `public=True` credentials are returned). Query params: `search` (name / name_override / description), `course` (course id), `program` (program id), `page`, `page_size`. - **GET** `…/{id}` — read one credential (`{id}` = `entity_id`). - **GET** `…/course-credentials/` — list credentials grouped by course. Query params: `search` (course name / course id), `page`, `page_size`. The paged `result` carries `results` (not `data`). - **GET** `…/images/` — search uploaded credential images. **Requires** a `query` param (case-insensitive name match); returns an array of `{id, name, image}`. ### Issuers & signatories — `…/issuers/` An issuer is required to create a credential (its `entityId` is the `issuer` field above). Issuers are keyed to the platform. - **GET** `…/issuers/` — list issuers for the org. Query params: `q` (search by name), plus `page`/`page_size`. Each issuer has `name`, `org`, `entityId`, `signatories`, `url`, `iconImage`, `allowed_template_tags`. - **GET** `…/issuers/{id}` — read one issuer (`{id}` = issuer `entity_id`, or its `org` identifier). ### Assignments (users & groups) — `…/assignments/` Assignments link a credential to users or groups; issuing an assignment produces assertions. Group assignments are processed asynchronously (Celery). Department admins only see/act on assignments within their department scope. - **GET** `…/assignments/users/` — list/search user assignments (also serves as the role-filtered **assertions for users** view — see Notes). Only assignments with `status == "COMPLETED"` that have a matching non-revoked assertion are returned. Supports `search` (credential name / username / email), `page`, `page_size`, and optional `platform_org`. - **GET** `…/assignments/groups/` — list group assignments (department-filtered). Optional `group_id` filter; `page`, `page_size`. ### Assertions — `…/assertions/` An assertion is an issued credential (a credential granted to a user). - **GET** `…/assertions/` — list all assertions for this user/org. Query params: `course` (filter by course id), `include_revoked` (default false), `include_expired` (default false), `exclude_main_tenant_assertions` (default false), `page`, `page_size`. Response fields are camelCase: `entityId`, `issuedOn`, `credentialDetails`, `recipient`, `course`, `program`, `revoked`, `revocationReason`, `acceptance`, `expires`, `metadata`, `narrative`. - **GET** `…/assertions/{id}` — read one assertion (`{id}` = assertion `entity_id`). - **GET** `/api/credentials/public/assertions/{id}/` — **public**, unauthenticated lookup of a single assertion by `entity_id` (no org/user segment, no auth header needed). Returns 404 if not found or revoked (revoked responses include the revocation reason). ### Course import/export (exim) — `/api/credentials/exim/…` Platform-agnostic; no org/user segment. **DM admin only.** Export converts item ids to course ids in the response; import converts course ids to item ids and replaces the issuer with the platform's default issuer. - **GET** `/api/credentials/exim/credentials/course/{course_id}/export/` — export all credential definitions for a course (returns `course_id` + a `credentials` array with `entityId`, `name`, templates, `expires`, `issuerDetails`, `signatories`, `courses`, `programs`, `pathways`, etc.). ### Provider config / external mapping — `…/provider-config/`, `…/external-mapping/` **Platform admin only** (`Requires platform admin access`). Path uses `…/users/{username}/…` where `{username}` is the admin making the request. These configure external issuers (e.g. Accredible) and map internal credentials to external templates. #### Available providers (public list) — `/api/credentials/providers/` - **GET** `/api/credentials/providers/` — list enabled credential providers (no org/user segment; only requires authentication, not admin). Optional `page`, `page_size` (default 50, max 1000). Each entry: `name`, `display_name`, `description`, `enabled`, `metadata`. #### Provider configuration — `…/provider-config/` - **GET** `…/provider-config/` — list provider configs. Optional `provider_name`, `page`, `page_size`. Each entry has `provider_name`, `provider_name_display`, `config` (provider settings), `enabled`. #### External credential mapping — `…/external-mapping/` - **GET** `…/external-mapping/` — list mappings. Optional `credential_id` (filter by credential entity_id), `provider_name`, `page`, `page_size`. ### Analytics — `…/` All take optional `start_date` / `end_date` (`YYYY-MM-DD`) query params and return `{ "data": { "": count, … } }` (back-filled with zero-value days when a range is given). - **GET** `…/assertions-over-time/` — assertion (issuance) counts over time. **Platform admin only.** Also returns a `meta` block with totals and percent changes. - **GET** `…/course-assertions-over-time/` — per-course assertion counts over time. **Platform admin only.** - **GET** `…/credentials-over-time/` — credential-creation counts over time. ## Writes ### Credentials — `…/` - **POST** `…/` — create a credential. Confirm with the user first. `name` and `issuer` (the **issuer entity_id**) are **required**; everything else is optional: ```json { "name": "string", "issuer": "issuer-entity-id", "description": "string", "criteriaUrl": "string", "criteriaNarrative": "string", "credentialType": "string", "signal": "COURSE_PASSED", "html_template": "string", "css_template": "string", "tags": [], "metadata": {}, "expires": { "amount": 365, "duration": "days" }, "iconImage": "url", "backgroundImage": "url", "thumbnailImage": "url", "courses": ["course-v1:org+course+run"], "programs": ["program-id"], "pathways": ["pathway-id"] } ``` `signal` defaults to `COURSE_PASSED`. Body field names are camelCase as shown (these map to the credential output fields). The response `result` uses `entityId`, `criteriaUrl`, `criteriaNarrative`, `credentialType`, `signal`, `issuerDetails`, `signatories`, `courses`, `programs`, `pathways`, `expires`, etc. - **PUT** `…/{id}` — update one credential (partial; only present fields change). Updatable fields: `name`, `description`, `html_template`, `css_template`, `credentialType`, `criteriaUrl`, `criteriaNarrative`, `signal`, `iconImage`, `backgroundImage`, `thumbnailImage`, `metadata` (merged into existing). - **DELETE** `…/{id}` — delete one credential. Confirm with the user first. Returns **409 Conflict** if the credential still has active (non-revoked, unexpired) assertions — revoke/delete those first. - **POST** `…/images/` — upload an image as multipart form data (`image` file, optional `name`); returns the created `{id, name, image}`. ### Issuers & signatories — `…/issuers/` - **POST** `…/issuers/` — create an issuer. Confirm with the user first. Body: `name` (and optionally `iconImage`, `email`, `url`, `allowed_template_tags`). - **PUT** `…/issuers/{id}` — update an issuer. Confirm with the user first. - **DELETE** `…/issuers/{id}` — delete an issuer. Confirm with the user first. - **POST** `…/issuers/authority/` — create a signatory (issuer authority). Confirm with the user first. Body: `name`, `title`, `signature` (image url) required; optionally `org`, `entityId` (issuer entity id), or `credential` (credential entity id) to associate it. If none of `org`/`entityId`/`credential` is given, it falls back to the platform from the URL. ### Assignments (users & groups) — `…/assignments/` - **POST** `…/assignments/users/` — assign a credential to users. Confirm with the user first: ```json { "credential_id": "credential-entity-id", "user_ids": [123, 456] } ``` `user_ids` are numeric user ids. Returns `result` with `successful_assignments`, `failed_assignments`, and `message`. - **POST** `…/assignments/groups/` — assign a credential to groups (async). Confirm with the user first: ```json { "credential_id": "credential-entity-id", "group_ids": ["group-id-1", "group-id-2"] } ``` `credential_id` and `group_ids` are both required (`group_ids` are strings). Returns `result` with `successful_assignments` / `failed_assignments`. - **DELETE** `…/assignments/{id}` — delete one assignment (`{id}` = the assignment `entity_id`; resolves to either a group or individual assignment). Confirm with the user first. (This endpoint is DELETE-only — there is no GET on a single assignment.) For a group assignment this cascades to all related user assignments and assertions; for an individual assignment it deletes the assignment and its assertion. The response is a simple `{ "status": { "success": true, "description": "…" } }`. ### Assertions — `…/assertions/` - **PUT** `…/assertions/{id}` — revoke an assertion. This is outward-facing — confirm with the user first. Body: `{ "revoked": true, "revocationReason": "string" }`. - **POST** `…/{id}/assertions/` — issue (create) an assertion for credential `{id}` (= credential `entity_id`). This issues a credential — confirm with the user first. Body requires a `recipient` object and an item the credential is attached to: ```json { "recipient": { "identity": "username" }, "course": "course-v1:org+course+run", "metadata": {} } ``` Instead of `course` you may pass `catalog_item` (item id), `program` / `program_id`, or `pathway` / `pathway_id` to identify the item. - **POST** `…/{id}/assertions/bulk/` — issue the credential to many users at once. Confirm with the user first. Body: `users` is a list of usernames, plus the same item field (`course` / `catalog_item` / `program` / `pathway`) and optional `metadata`. Returns `{ "skipped": [...], "issued": [...] }`. ### Course import/export (exim) — `/api/credentials/exim/…` - **POST** `/api/credentials/exim/credentials/course/{course_id}/import/` — import credential definitions into a course. Confirm with the user first: ```json { "course_id": "course-v1:org+course+run", "credentials": [ { "entityId": "credential-entity-id", "name": "Credential Name", "description": "string", "criteriaUrl": "string", "criteriaNarrative": "string", "credentialType": "COURSE_CERTIFICATE", "expires": { "amount": 365, "duration": "days" }, "signatories": [ { "entityId": "authority-entity-id", "name": "string", "title": "string", "signature": "url" } ], "html_template": "string", "css_template": "string", "signal": "COURSE_PASSED", "courses": [ { "name": "string", "course_id": "course-v1:org+course+run" } ] } ] } ``` Existing credentials (matched by name + course + default issuer) are updated; missing ones are created. Response `result` lists `course_id`, `imported_credentials` (each with `entityId`, `name`, and `status` = `created`|`updated`) and `errors`. Status is 201 if all succeed, **207 Multi-Status** if some succeed with errors, 400 if none succeed. ### Provider config / external mapping — `…/provider-config/`, `…/external-mapping/` #### Provider configuration — `…/provider-config/` - **POST** `…/provider-config/` — create or update a provider config (201 on create, 200 on update). Confirm with the user first: ```json { "provider_name": "accredible", "config": { "accredible_api_key": "your-api-key", "accredible_base_url": "https://dashboard.accredible.com/", "accredible_api_base_url": "https://api.accredible.com/v1/", "accredible_group_id": "123456", "accredible_template_id": "789012", "use_sandbox": false }, "enabled": true } ``` `provider_name` is required; `config` and `enabled` are each optional. Config keys are normalized to lowercase; values are preserved verbatim. For `provider_name: "accredible"`, `config` must include `accredible_api_key` (validation rejects it otherwise). Returns 201 + description `Created` on create, 200 + `Updated` on update. - **DELETE** `…/provider-config/` — deactivate a provider config (sets `enabled=false`; external issuance is then skipped; the record is not removed). Confirm with the user first. Body: `{ "provider_name": "accredible" }`. Returns description `Deactivated` with the updated config object. #### External credential mapping — `…/external-mapping/` - **POST** `…/external-mapping/` — create or update a mapping (201 on create, 200 on update; unique per credential + platform + provider). Confirm with the user first: ```json { "credential_id": "credential-entity-id", "provider_name": "accredible", "external_template_id": "123456789", "group_id": "679866", "metadata": {} } ``` `credential_id` and `provider_name` are required; `external_template_id`, `group_id`, and `metadata` are optional. - **DELETE** `…/external-mapping/` — delete a mapping. Confirm with the user first. Body: `{ "credential_id": "credential-entity-id", "provider_name": "accredible" }`. ## Example List a user's issued credentials (assertions), searching for "python": ```bash curl -G \ "https://api.iblai.app/dm/api/credentials/orgs/$IBLAI_ORG/users/$IBLAI_USERNAME/assignments/users/" \ -H "Authorization: Api-Token $IBLAI_API_KEY" \ --data-urlencode "search=python" \ --data-urlencode "page_size=20" ``` ## Notes - **Role-based assertion filtering** (`…/assignments/users/`): the `{user}` in the path is the user whose role determines visibility — platform admins see assignments for all users in the org; department admins see only users in their department groups; everyone else sees only their own. If the resolved set is empty the call returns 403 ("You don't have permission to view these assignments"). - **Ids:** detail lookups for credentials, assertions, and assignments are **by `entity_id`** (UUID) only — numeric database ids are not accepted. Course ids are Open edX ids (`course-v1:org+course+run`). - **`{org}`** is the `platform_key` URL segment; it resolves against `Platform.key` first, then `Platform.org` as a fallback, and may also be passed as the `platform_org` query param. - **Accredible provider specifics:** `config` keys are lowercased on save (values preserved); when issuing, the system uses the mapping's `group_id` first and falls back to the provider config's `accredible_group_id`. `use_sandbox` toggles the Accredible sandbox. Disabling a provider config (`enabled=false`) skips external issuance for that org. - **Pagination defaults differ:** assignment/assertion lists default to `page_size=10` (max 100); provider-config and external-mapping lists default to `50` (max 1000). - **Group assignments are asynchronous** (Celery); the immediate response confirms acceptance, not completion. - Errors use the standard codes: 400 (bad input), 401 (unauthenticated), 403 (insufficient permissions, e.g. "Requires platform admin access"), 404 (not found), 500 (server error).