{"openapi":"3.1.0","info":{"title":"Kapi Works API","version":"1.2.0","summary":"Read-only employee training evidence for a company's own systems.","description":"Read a company's employee training records, deadlines, completions, certificates and people from your own server. Calls are limited to 60 a minute per key, 120 per company and 2 at once per company. Pages hold at most 200 rows and 3 MiB; periods are at most 400 days. Keys expire within one year. Completion is when Kapi received it, including offline sync. Names and work emails come from current source rows; erased people are labelled Erased person. History before history_complete_from is unavailable, not empty. v1 adds fields and enum values without notice: ignore unknown fields, and treat unknown outcome values as needing review, never as completed. Breaking changes get a new major version and at least 12 months' notice.","contact":{"name":"Kapi Works support","email":"support@kapiworks.com","url":"https://docs.kapiworks.com/support"}},"servers":[{"url":"https://api.kapiworks.com","description":"Kapi Works API"}],"externalDocs":{"description":"Kapi Works developer docs","url":"https://docs.kapiworks.com"},"tags":[{"name":"Training records","description":"What each employee owes and what they did about it."},{"name":"Certificates","description":"Certificates issued to employees, with their state at a moment."},{"name":"People","description":"Employees and invitations, for matching against your own roster."},{"name":"Courses","description":"The published course versions that training records refer to."},{"name":"Snapshot","description":"A cheap check of whether anything changed since your last pull."}],"x-limits":{"per_key_per_minute":60,"per_company_per_minute":120,"concurrent_per_company":2,"max_page_size":200,"max_period_days":400,"max_response_bytes":3145728,"call_log_retention_months":13,"key_max_lifetime_days":365,"active_keys_per_company":10,"deprecation_notice_months":12},"x-error-codes":[{"code":"invalid_request","status":400,"title":"Invalid request","detail":"Check the query values. Send API keys only in the Authorization header.","action":"Fix the query and send it again. Unknown or repeated parameters, malformed dates and IDs, and periods longer than 400 days are refused.","type":"https://docs.kapiworks.com/errors/invalid_request"},{"code":"invalid_cursor","status":400,"title":"Invalid cursor","detail":"Use the cursor returned by the previous page with the same filters.","action":"Start the pull again without a cursor. A cursor works only on the endpoint and filters that produced it, for the same company.","type":"https://docs.kapiworks.com/errors/invalid_cursor"},{"code":"key_in_url","status":400,"title":"API key revoked","detail":"The API key was sent in the URL, so Kapi revoked it.","action":"A URL can be stored in logs and browser history. Ask the company administrator for a new key, and send it only in the Authorization header.","type":"https://docs.kapiworks.com/errors/key_in_url"},{"code":"invalid_token","status":401,"title":"Invalid API key","detail":"The API key is invalid or no longer active.","action":"Stop polling and ask the company administrator for a new key. Unknown, expired and revoked keys get the same answer.","type":"https://docs.kapiworks.com/errors/invalid_token"},{"code":"insufficient_scope","status":403,"title":"Scope required","detail":"This key does not have the scope required for this register.","action":"Ask the company administrator for a key with the scope named in the WWW-Authenticate header. Retrying the same key does not help.","type":"https://docs.kapiworks.com/errors/insufficient_scope"},{"code":"ip_not_allowed","status":403,"title":"Network not allowed","detail":"This key works only from the networks its company chose.","action":"Call from one of the key's networks, or ask the company administrator for a key that allows yours.","type":"https://docs.kapiworks.com/errors/ip_not_allowed"},{"code":"not_found","status":404,"title":"Not found","detail":"This API address is unavailable.","action":"Use a documented path on the API host.","type":"https://docs.kapiworks.com/errors/not_found"},{"code":"method_not_allowed","status":405,"title":"Method not allowed","detail":"Use GET or HEAD for this API.","action":"The API is read-only. The Allow header lists the methods it accepts.","type":"https://docs.kapiworks.com/errors/method_not_allowed"},{"code":"before_history","status":422,"title":"History unavailable","detail":"Records are not available before collection began.","action":"Use history_complete_from as the earliest moment you ask for. Earlier records are unavailable, not empty.","type":"https://docs.kapiworks.com/errors/before_history"},{"code":"record_too_large","status":422,"title":"Record too large","detail":"A record exceeds this API's response limit. Use a records pack and report the request ID to Kapi.","action":"Use a records pack for this register, and report the request ID to Kapi support.","type":"https://docs.kapiworks.com/errors/record_too_large"},{"code":"rate_limited","status":429,"title":"Too many requests","detail":"This key or its company has used its calls for this minute.","action":"Wait at least Retry-After seconds, then add a small random delay. Refused calls do not count against the limit.","type":"https://docs.kapiworks.com/errors/rate_limited"},{"code":"concurrent_limit","status":429,"title":"Too many requests at once","detail":"Two calls are already running for this company.","action":"Wait for the running calls to finish, then retry after Retry-After seconds. Run one pull at a time per company.","type":"https://docs.kapiworks.com/errors/concurrent_limit"},{"code":"read_failed","status":502,"title":"Records unavailable","detail":"The records could not be read. Try again.","action":"Keep your last complete pull. Retry with exponential backoff, and report the request ID if it keeps failing.","type":"https://docs.kapiworks.com/errors/read_failed"},{"code":"unavailable","status":503,"title":"API unavailable","detail":"The API is not configured for this deployment.","action":"Contact Kapi support with the request ID. Retrying does not help until the configuration is fixed.","type":"https://docs.kapiworks.com/errors/unavailable"},{"code":"upstream_unavailable","status":503,"title":"Temporarily unavailable","detail":"Kapi could not reach its records service. Try again shortly.","action":"Retry after Retry-After seconds with exponential backoff and jitter. Keep your last complete pull meanwhile.","type":"https://docs.kapiworks.com/errors/upstream_unavailable"},{"code":"writer_unavailable","status":503,"title":"History catching up","detail":"Recent changes are still being recorded. Try again shortly.","action":"Retry after Retry-After seconds. A pull already in progress can still finish from its cursor.","type":"https://docs.kapiworks.com/errors/writer_unavailable"},{"code":"read_timeout","status":504,"title":"Read timed out","detail":"The page took too long to read. Try again with a smaller page.","action":"Retry with a smaller limit, then return to your usual page size.","type":"https://docs.kapiworks.com/errors/read_timeout"}],"x-data-dictionary":{"training":{"deployment_id":"Kapi deployment UUID.","organization_id":"Company UUID; the tenant boundary.","learning_space_id":"Learning-space UUID.","record_id":"Stable owed-record UUID: person, course and published release, or renewal cycle.","learner_profile_id":"Employee profile UUID; retained after erasure.","person_name":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then.","work_email":"Address on the current source employee invitation; not the Auth account email. Null for an erased person.","course_id":"Course UUID.","course_release_id":"Immutable published course version UUID.","renews_certificate_id":"Certificate whose renewal created this owed record.","sequence":"Sequence of this stored row version in the company history; snapshot.sequence is the upper boundary for the pull.","valid_from":"When these facts took effect.","recorded_at":"When the history writer captured them.","cause":"first, change or sweep.","logic_version":"Version of the shared owed-learning decider.","requirement":"required, optional, no_longer_required or superseded.","assigned_at":"When the requirement began.","due_at":"Deadline instant, or null for no deadline.","due_time_zone":"IANA company/deadline time zone.","due_basis":"share_deadline, days_after_added, renewal, override or none.","completed_at":"Time Kapi’s server received the completion, including offline completions received later.","completion_receipt_id":"Server completion receipt UUID.","ended_at":"When the record stopped applying.","ended_reason":"Fixed reason code, never typed text; source_removed when the person was erased.","share_ids":"JSON array of all contributing share UUIDs.","doors":"JSON array of audience doors; includes employee.","certificate_id":"Certificate UUID.","obligation_change_id":"Source obligation-change ID.","waived_at":"When an administrator changed the obligation.","waiver_actor_id":"Administrator UUID.","waiver_reason_code":"Fixed waiver reason code.","outcome":"completed_on_time, completed_late, completed_before_requirement, not_due_yet, overdue, missed, excused, waived or optional_not_done.","days_late":"Ceiling of elapsed days past the deadline at completion or the end of the period/obligation.","reminders_sent":"Number of completed reminder sends known at the pinned capture."},"certificates":{"certificate_id":"Certificate UUID.","deployment_id":"Kapi deployment UUID.","organization_id":"Company UUID; the tenant boundary.","learning_space_id":"Learning-space UUID.","learner_profile_id":"Employee profile UUID; retained after erasure.","person_name":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then.","course_id":"Course UUID.","course_release_id":"Immutable published course version UUID.","course_release_version":"Published course version number.","certificate_title":"Title recorded on the certificate edition.","kind":"completion or assessed.","earned_at":"Completion or assessment time.","issued_at":"Certificate issue time.","valid_until":"Fixed expiry instant; null means no expiry.","status":"Certificate state at the requested moment: valid, expired, revoked or replaced.","changed_at":"Revocation/replacement time, when known at this snapshot.","changed_by":"Actor UUID for revocation/replacement.","supersedes_id":"Earlier certificate corrected by this certificate.","renews_id":"Earlier certificate renewed by this certificate.","check_code":"Certificate check code. Returned only with people:read because it opens the public check page, which shows the holder's name. The check page shows nothing once the holder is erased.","live_session_id":"Live session UUID when the person finished the course by attending a live session in full; null otherwise.","live_session_format":"online or on_site.","live_session_starts_at":"When the live session started.","live_session_ends_at":"When the live session ended.","live_session_minutes":"Length of the live session in minutes.","live_session_marked_by":"Who made the final register mark: trainer, or kapi when Kapi changed it after an ask.","trainer_name":"Trainer of the live session, from their current Kapi profile; A Kapi expert until they set a name."},"people":{"deployment_id":"Kapi deployment UUID.","organization_id":"Company UUID; the tenant boundary.","learning_space_id":"Learning-space UUID.","person_id":"Stable employee or invitation row UUID.","learner_profile_id":"Employee profile UUID; retained after erasure.","invitation_id":"Invitation UUID for invitation rows.","sequence":"Sequence of this stored row version in the company history; snapshot.sequence is the upper boundary for the pull.","valid_from":"When these facts took effect.","recorded_at":"When the history writer captured them.","cause":"first, change or sweep.","logic_version":"Version of the shared owed-learning decider.","entry_type":"employee or invitation; accepted invitations and employees are distinct source rows.","person_name":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then.","work_email":"Address on the current source employee invitation; not the Auth account email. Null for an erased person.","invited_at":"Invitation creation time.","joined_at":"When the person joined Kapi; not an employment start date.","left_at":"When access ended in Kapi.","invitation_expires_at":"Invitation expiry time.","invitation_state":"Stored invitation state.","invitation_status":"Invitation state at the requested moment, including expiry.","access_state":"active, suspended, ended, erased, invited or staff_practice. erased means Kapi removed the person at the company’s instruction.","doors":"JSON array of audience doors; includes employee.","team_ids":"JSON array of company team UUIDs.","records_owed":"Open required employee records at the requested moment.","records_completed":"Completed employee records at the requested moment.","no_required_records":"True when no required records are open."},"courses":{"row_id":"Stable course release row UUID.","course_id":"Course UUID.","course_release_id":"Immutable published course version UUID.","course_title":"Title from the current source course.","version":"Published version number.","published_at":"Time the release was sealed.","publisher_id":"Publisher UUID.","content_sha256":"Immutable course content fingerprint.","contents_sha256":"Fingerprint of the list of contained releases.","source_kind":"course_release, release_unavailable or lesson_share_without_course_release.","lessons":"JSON array of lesson release IDs, current source titles, versions and content SHA-256 values."}},"paths":{"/v1/training-records":{"get":{"operationId":"listTrainingRecords","summary":"Training records at a moment","tags":["Training records"],"description":"Every record an employee owes at `as_at`, with its outcome then. Requires `training:read`. Names, work emails and certificate check codes also need `people:read`; without it they are left out.","x-required-scope":"training:read","security":[{"bearerAuth":["training:read"]}],"parameters":[{"name":"as_at","in":"query","required":false,"description":"RFC 3339 instant with an offset, or a date read as the end of the company's day. Defaults to now.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"course","in":"query","required":false,"description":"Optional course UUID. The cursor keeps it.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the previous page. Send it unchanged to the same endpoint. It pins the snapshot and filters, and grants no access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1 to 200. Larger values are treated as 200. A page can hold fewer rows to stay within 3 MiB, so follow next_cursor until it is null.","schema":{"type":"integer","minimum":1,"default":200}}],"responses":{"200":{"description":"One page from a pinned company snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRecordPage"},"example":{"data":[{"deployment_id":"00000000-0000-4000-8000-000000000001","organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","learning_space_id":"8b2d6c3e-5f41-4a8e-9c27-1d0e3f4a5b62","record_id":"a7e3c1b9-2d48-4f6a-8e15-9b0c7d6e5f43","learner_profile_id":"c3a1e7f2-8d64-4b1f-a9e2-6f5d4c3b2a10","person_name":"Amira Okafor","work_email":"amira.okafor@example.com","course_id":"d2f8b4a6-7c1e-4e93-b5a0-3c9e8f7d6a21","course_release_id":"e5c2a9d7-4b3f-4c18-9a6e-2f1d0c9b8a77","renews_certificate_id":null,"sequence":1271,"valid_from":"2026-09-12T13:41:09Z","recorded_at":"2026-09-12T13:41:12Z","cause":"change","logic_version":4,"requirement":"required","assigned_at":"2026-09-01T08:00:00Z","due_at":"2026-09-30T22:59:59Z","due_time_zone":"Europe/London","due_basis":"share_deadline","completed_at":"2026-09-12T13:41:09Z","completion_receipt_id":"9c8b7a6f-5e4d-4c3b-8a29-1f0e9d8c7b66","ended_at":null,"ended_reason":null,"waived_at":null,"waiver_actor_id":null,"waiver_reason_code":null,"outcome":"completed_on_time","days_late":null,"reminders_sent":1,"share_ids":["b6d4f2a8-9e1c-4a35-8f72-0e9d8c7b6a54"],"doors":["employee"],"certificate_id":"f1b9d3e5-6a2c-4d70-8b4e-5c3a2f1e0d98","obligation_change_id":null}],"total":1,"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true},"next_cursor":null}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}},"/v1/training-records/period":{"get":{"operationId":"listPeriodTrainingRecords","summary":"Every training record owed during a period","tags":["Training records"],"description":"Every record owed at any time between `from` and `to`, with its outcome at the end of the period or when it ended. Requires `training:read`. Names, work emails and certificate check codes also need `people:read`; without it they are left out.","x-required-scope":"training:read","security":[{"bearerAuth":["training:read"]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Required on the first page. Period start, inclusive; a date starts the company's day. Omit it with a cursor, which keeps it. At most 400 days before `to`.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"to","in":"query","required":false,"description":"Required on the first page. Period end, inclusive, as an instant or a company date. Omit it with a cursor, which keeps it.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"course","in":"query","required":false,"description":"Optional course UUID. The cursor keeps it.","schema":{"type":"string","format":"uuid"}},{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the previous page. Send it unchanged to the same endpoint. It pins the snapshot and filters, and grants no access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1 to 200. Larger values are treated as 200. A page can hold fewer rows to stay within 3 MiB, so follow next_cursor until it is null.","schema":{"type":"integer","minimum":1,"default":200}}],"responses":{"200":{"description":"One page from a pinned company snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrainingRecordPage"},"example":{"data":[{"deployment_id":"00000000-0000-4000-8000-000000000001","organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","learning_space_id":"8b2d6c3e-5f41-4a8e-9c27-1d0e3f4a5b62","record_id":"a7e3c1b9-2d48-4f6a-8e15-9b0c7d6e5f43","learner_profile_id":"c3a1e7f2-8d64-4b1f-a9e2-6f5d4c3b2a10","person_name":"Amira Okafor","work_email":"amira.okafor@example.com","course_id":"d2f8b4a6-7c1e-4e93-b5a0-3c9e8f7d6a21","course_release_id":"e5c2a9d7-4b3f-4c18-9a6e-2f1d0c9b8a77","renews_certificate_id":null,"sequence":1271,"valid_from":"2026-09-12T13:41:09Z","recorded_at":"2026-09-12T13:41:12Z","cause":"change","logic_version":4,"requirement":"required","assigned_at":"2026-09-01T08:00:00Z","due_at":"2026-09-30T22:59:59Z","due_time_zone":"Europe/London","due_basis":"share_deadline","completed_at":"2026-09-12T13:41:09Z","completion_receipt_id":"9c8b7a6f-5e4d-4c3b-8a29-1f0e9d8c7b66","ended_at":null,"ended_reason":null,"waived_at":null,"waiver_actor_id":null,"waiver_reason_code":null,"outcome":"completed_on_time","days_late":null,"reminders_sent":1,"share_ids":["b6d4f2a8-9e1c-4a35-8f72-0e9d8c7b6a54"],"doors":["employee"],"certificate_id":"f1b9d3e5-6a2c-4d70-8b4e-5c3a2f1e0d98","obligation_change_id":null}],"total":1,"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true},"next_cursor":null}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}},"/v1/certificates":{"get":{"operationId":"listCertificates","summary":"Certificates at a moment","tags":["Certificates"],"description":"Employee certificates issued by `as_at`, with their state then. Requires `certificates:read`. Names, work emails and certificate check codes also need `people:read`; without it they are left out.","x-required-scope":"certificates:read","security":[{"bearerAuth":["certificates:read"]}],"parameters":[{"name":"as_at","in":"query","required":false,"description":"RFC 3339 instant with an offset, or a date read as the end of the company's day. Defaults to now.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the previous page. Send it unchanged to the same endpoint. It pins the snapshot and filters, and grants no access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1 to 200. Larger values are treated as 200. A page can hold fewer rows to stay within 3 MiB, so follow next_cursor until it is null.","schema":{"type":"integer","minimum":1,"default":200}}],"responses":{"200":{"description":"One page from a pinned company snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CertificatePage"},"example":{"data":[{"deployment_id":"00000000-0000-4000-8000-000000000001","organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","learning_space_id":"8b2d6c3e-5f41-4a8e-9c27-1d0e3f4a5b62","certificate_id":"f1b9d3e5-6a2c-4d70-8b4e-5c3a2f1e0d98","learner_profile_id":"c3a1e7f2-8d64-4b1f-a9e2-6f5d4c3b2a10","person_name":"Amira Okafor","course_id":"d2f8b4a6-7c1e-4e93-b5a0-3c9e8f7d6a21","course_release_id":"e5c2a9d7-4b3f-4c18-9a6e-2f1d0c9b8a77","course_release_version":3,"certificate_title":"Information security essentials","kind":"completion","earned_at":"2026-09-12T13:41:09Z","issued_at":"2026-09-12T13:41:10Z","valid_until":"2027-09-12T13:41:10Z","status":"valid","changed_at":null,"changed_by":null,"supersedes_id":null,"renews_id":null,"check_code":"KW7Q4M2X9P","live_session_id":null,"live_session_format":null,"live_session_starts_at":null,"live_session_ends_at":null,"live_session_minutes":null,"live_session_marked_by":null,"trainer_name":null}],"total":1,"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true},"next_cursor":null}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}},"/v1/people":{"get":{"operationId":"listPeople","summary":"Employees and invitations during a period","tags":["People"],"description":"Employees and invitation records that existed between `from` and `to`. Requires `people:read`. Names, work emails and certificate check codes also need `people:read`; without it they are left out.","x-required-scope":"people:read","security":[{"bearerAuth":["people:read"]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Required on the first page. Period start, inclusive; a date starts the company's day. Omit it with a cursor, which keeps it. At most 400 days before `to`.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"to","in":"query","required":false,"description":"Required on the first page. Period end, inclusive, as an instant or a company date. Omit it with a cursor, which keeps it.","schema":{"anyOf":[{"type":"string","format":"date-time","title":"date-time"},{"type":"string","format":"date","title":"date"}]}},{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the previous page. Send it unchanged to the same endpoint. It pins the snapshot and filters, and grants no access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1 to 200. Larger values are treated as 200. A page can hold fewer rows to stay within 3 MiB, so follow next_cursor until it is null.","schema":{"type":"integer","minimum":1,"default":200}}],"responses":{"200":{"description":"One page from a pinned company snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PersonPage"},"example":{"data":[{"deployment_id":"00000000-0000-4000-8000-000000000001","organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","learning_space_id":"8b2d6c3e-5f41-4a8e-9c27-1d0e3f4a5b62","person_id":"c3a1e7f2-8d64-4b1f-a9e2-6f5d4c3b2a10","learner_profile_id":"c3a1e7f2-8d64-4b1f-a9e2-6f5d4c3b2a10","invitation_id":null,"entry_type":"employee","sequence":1190,"valid_from":"2026-08-28T10:02:00Z","recorded_at":"2026-08-28T10:02:03Z","cause":"change","logic_version":4,"person_name":"Amira Okafor","work_email":"amira.okafor@example.com","invited_at":"2026-08-27T09:15:00Z","joined_at":"2026-08-28T10:02:00Z","left_at":null,"invitation_expires_at":null,"invitation_state":null,"invitation_status":null,"access_state":"active","team_ids":["b6d4f2a8-9e1c-4a35-8f72-0e9d8c7b6a54"],"doors":["employee"],"records_owed":4,"records_completed":3,"no_required_records":false}],"total":1,"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true},"next_cursor":null}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}},"/v1/courses":{"get":{"operationId":"listCourses","summary":"Course versions referenced by employee records","tags":["Courses"],"description":"The published course versions behind employee training records, with their content fingerprints. Requires `training:read`. Names, work emails and certificate check codes also need `people:read`; without it they are left out.","x-required-scope":"training:read","security":[{"bearerAuth":["training:read"]}],"parameters":[{"name":"cursor","in":"query","required":false,"description":"Opaque next_cursor from the previous page. Send it unchanged to the same endpoint. It pins the snapshot and filters, and grants no access.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Rows per page, 1 to 200. Larger values are treated as 200. A page can hold fewer rows to stay within 3 MiB, so follow next_cursor until it is null.","schema":{"type":"integer","minimum":1,"default":200}}],"responses":{"200":{"description":"One page from a pinned company snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoursePage"},"example":{"data":[{"row_id":"e5c2a9d7-4b3f-4c18-9a6e-2f1d0c9b8a77","course_id":"d2f8b4a6-7c1e-4e93-b5a0-3c9e8f7d6a21","course_release_id":"e5c2a9d7-4b3f-4c18-9a6e-2f1d0c9b8a77","course_title":"Information security essentials","version":3,"published_at":"2026-08-20T16:30:00Z","publisher_id":"7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c21","content_sha256":"8d2f6a0c4e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a3c5e7b9d2f","contents_sha256":"1a3c5e7b9d2f4a6c8e0b2d4f6a8c0e2b4d6f8a1c3e5b7d9f0a2c4e6b8d1f3a5c","source_kind":"course_release","lessons":[{"release_id":"5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b10","title":"Spotting a phishing email","version":2,"content_sha256":"6c8e0b2d4f6a8c0e2b4d6f8a1c3e5b7d9f0a2c4e6b8d1f3a5c7e9b2d4f6a8c0e"}]}],"total":1,"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true},"next_cursor":null}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}},"/v1/snapshot":{"get":{"operationId":"getSnapshot","summary":"Check whether anything changed","tags":["Snapshot"],"description":"Returns the company's latest snapshot in one call, with any scope. If `sequence` and `capture_sequence` match your last complete pull, no recorded facts have changed since. Outcomes that depend on the clock, such as `not_due_yet` becoming `overdue`, can still change, so recompute them from `due_at` or pull again at least daily.","security":[{"bearerAuth":[]}],"responses":{"200":{"description":"The latest snapshot.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}},"Sunset":{"$ref":"#/components/headers/Sunset"},"Link":{"$ref":"#/components/headers/Link"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SnapshotResponse"},"example":{"snapshot":{"organization_id":"4f6b2a52-1c1e-4f7a-9d3e-2b7c8a9e0d11","deployment_id":"00000000-0000-4000-8000-000000000001","as_at":"2026-10-09T09:00:00Z","from":null,"known_at":"2026-10-09T08:59:41Z","sequence":1284,"capture_sequence":352,"chain_hash":"3f9a1c7e5b2d8f4a6c0e9b1d3f5a7c9e2b4d6f8a0c1e3b5d7f9a2c4e6b8d0f1a","logic":{"version":4,"date":"2026-10-06"},"history_complete_from":"2026-10-06T05:00:00Z","available":true}}}}},"400":{"description":"RFC 9457 problem details. Codes: invalid_request, invalid_cursor, key_in_url. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_request","title":"Invalid request","status":400,"detail":"Check the query values. Send API keys only in the Authorization header.","code":"invalid_request"}}}},"401":{"description":"RFC 9457 problem details. Codes: invalid_token. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/invalid_token","title":"Invalid API key","status":401,"detail":"The API key is invalid or no longer active.","code":"invalid_token"}}}},"403":{"description":"RFC 9457 problem details. Codes: insufficient_scope, ip_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"WWW-Authenticate":{"description":"Bearer challenge. A scope refusal names the scope it needs.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/insufficient_scope","title":"Scope required","status":403,"detail":"This key does not have the scope required for this register.","code":"insufficient_scope"}}}},"404":{"description":"RFC 9457 problem details. Codes: not_found. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/not_found","title":"Not found","status":404,"detail":"This API address is unavailable.","code":"not_found"}}}},"405":{"description":"RFC 9457 problem details. Codes: method_not_allowed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Allow":{"schema":{"const":"GET, HEAD"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/method_not_allowed","title":"Method not allowed","status":405,"detail":"Use GET or HEAD for this API.","code":"method_not_allowed"}}}},"422":{"description":"RFC 9457 problem details. Codes: before_history, record_too_large. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/before_history","title":"History unavailable","status":422,"detail":"Records are not available before collection began.","code":"before_history"}}}},"429":{"description":"RFC 9457 problem details. Codes: rate_limited, concurrent_limit. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}},"RateLimit-Policy":{"description":"Provisional, following the IETF RateLimit draft: the per-key, per-company and concurrency quotas.","schema":{"type":"string"}},"RateLimit":{"description":"Provisional, following the IETF RateLimit draft: calls remaining (r) and seconds until a call frees up (t), per key and per company.","schema":{"type":"string"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/rate_limited","title":"Too many requests","status":429,"detail":"This key or its company has used its calls for this minute.","code":"rate_limited"}}}},"502":{"description":"RFC 9457 problem details. Codes: read_failed. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_failed","title":"Records unavailable","status":502,"detail":"The records could not be read. Try again.","code":"read_failed"}}}},"503":{"description":"RFC 9457 problem details. Codes: unavailable, upstream_unavailable, writer_unavailable. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}},"Retry-After":{"description":"Minimum delay in seconds before retrying.","schema":{"type":"string","pattern":"^[0-9]+$"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/unavailable","title":"API unavailable","status":503,"detail":"The API is not configured for this deployment.","code":"unavailable"}}}},"504":{"description":"RFC 9457 problem details. Codes: read_timeout. Act on `code` or `type`; never parse `detail`.","headers":{"X-Request-Id":{"description":"Request UUID for support and correlation. It is not a credential.","schema":{"type":"string","format":"uuid"}},"Cache-Control":{"schema":{"const":"no-store"}},"X-Content-Type-Options":{"schema":{"const":"nosniff"}}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"},"example":{"type":"https://docs.kapiworks.com/errors/read_timeout","title":"Read timed out","status":504,"detail":"The page took too long to read. Try again with a smaller page.","code":"read_timeout"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"A company API key, not an OAuth access token: kw_live_ in production, kw_test_ elsewhere. Send it only in the Authorization header. A key sent in a URL is revoked."}},"headers":{"Sunset":{"description":"The day Kapi deletes this company's data. Present only while the company's contract is ending.","schema":{"type":"string"},"example":"Wed, 30 Dec 2026 23:59:59 GMT"},"Link":{"description":"The guide to what happens when a company's contract ends, with rel=\"sunset\". Present only with Sunset.","schema":{"type":"string"},"example":"<https://docs.kapiworks.com/guides/company-ends>; rel=\"sunset\""}},"schemas":{"Problem":{"type":"object","required":["type","title","status","detail","code"],"properties":{"type":{"type":"string","format":"uri","description":"https://docs.kapiworks.com/errors/<code>: the page explaining this error."},"title":{"type":"string"},"status":{"type":"integer","minimum":0},"detail":{"type":"string"},"code":{"type":"string","examples":["invalid_request","invalid_cursor","key_in_url","invalid_token","insufficient_scope","ip_not_allowed","not_found","method_not_allowed","before_history","record_too_large","rate_limited","concurrent_limit","read_failed","unavailable","upstream_unavailable","writer_unavailable","read_timeout"],"description":"Stable, machine-readable error code."},"history_complete_from":{"type":"string","format":"date-time","description":"Sent with before_history: the earliest moment the API can answer for."}}},"Snapshot":{"type":"object","required":["organization_id","deployment_id","as_at","sequence","capture_sequence","chain_hash","logic","history_complete_from"],"properties":{"organization_id":{"type":"string","format":"uuid"},"deployment_id":{"type":"string","format":"uuid"},"as_at":{"type":"string","format":"date-time"},"from":{"type":["string","null"],"format":"date-time"},"known_at":{"type":"string","format":"date-time"},"sequence":{"type":"integer","minimum":0},"capture_sequence":{"type":"integer","minimum":0},"chain_hash":{"type":"string","pattern":"^[a-f0-9]{64}$"},"logic":{"type":"object","required":["version","date"],"properties":{"version":{"type":"integer","minimum":0},"date":{"type":"string","format":"date"}}},"history_complete_from":{"type":"string","format":"date-time"},"available":{"type":"boolean"}}},"SnapshotResponse":{"type":"object","required":["snapshot"],"properties":{"snapshot":{"$ref":"#/components/schemas/Snapshot"}}},"TrainingRecordPage":{"type":"object","required":["data","total","snapshot","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/TrainingRecord"}},"total":{"type":"integer","minimum":0,"description":"Rows in the whole pull, not this page."},"snapshot":{"$ref":"#/components/schemas/Snapshot"},"next_cursor":{"type":["string","null"],"description":"Send it to get the next page. Null means the pull is complete."}}},"CertificatePage":{"type":"object","required":["data","total","snapshot","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Certificate"}},"total":{"type":"integer","minimum":0,"description":"Rows in the whole pull, not this page."},"snapshot":{"$ref":"#/components/schemas/Snapshot"},"next_cursor":{"type":["string","null"],"description":"Send it to get the next page. Null means the pull is complete."}}},"PersonPage":{"type":"object","required":["data","total","snapshot","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Person"}},"total":{"type":"integer","minimum":0,"description":"Rows in the whole pull, not this page."},"snapshot":{"$ref":"#/components/schemas/Snapshot"},"next_cursor":{"type":["string","null"],"description":"Send it to get the next page. Null means the pull is complete."}}},"CoursePage":{"type":"object","required":["data","total","snapshot","next_cursor"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Course"}},"total":{"type":"integer","minimum":0,"description":"Rows in the whole pull, not this page."},"snapshot":{"$ref":"#/components/schemas/Snapshot"},"next_cursor":{"type":["string","null"],"description":"Send it to get the next page. Null means the pull is complete."}}},"TrainingRecord":{"type":"object","required":["record_id","learner_profile_id","course_id","requirement","outcome"],"properties":{"deployment_id":{"type":"string","format":"uuid","description":"Kapi deployment UUID."},"organization_id":{"type":"string","format":"uuid","description":"Company UUID; the tenant boundary."},"learning_space_id":{"type":"string","format":"uuid","description":"Learning-space UUID."},"sequence":{"type":"integer","minimum":0,"description":"Sequence of this stored row version in the company history; snapshot.sequence is the upper boundary for the pull."},"valid_from":{"type":"string","format":"date-time","description":"When these facts took effect."},"recorded_at":{"type":"string","format":"date-time","description":"When the history writer captured them."},"cause":{"type":"string","examples":["first","change","sweep"],"description":"first, change or sweep."},"logic_version":{"type":"integer","minimum":0,"description":"Version of the shared owed-learning decider."},"record_id":{"type":"string","format":"uuid","description":"Stable owed-record UUID: person, course and published release, or renewal cycle."},"learner_profile_id":{"type":"string","format":"uuid","description":"Employee profile UUID; retained after erasure."},"person_name":{"type":["string","null"],"description":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then."},"work_email":{"type":["string","null"],"description":"Address on the current source employee invitation; not the Auth account email. Null for an erased person."},"course_id":{"type":"string","format":"uuid","description":"Course UUID."},"course_release_id":{"type":["string","null"],"format":"uuid","description":"Immutable published course version UUID."},"renews_certificate_id":{"type":["string","null"],"format":"uuid","description":"Certificate whose renewal created this owed record."},"requirement":{"type":"string","examples":["required","optional","no_longer_required","superseded"],"description":"required, optional, no_longer_required or superseded."},"assigned_at":{"type":"string","format":"date-time","description":"When the requirement began."},"due_at":{"type":["string","null"],"format":"date-time","description":"Deadline instant, or null for no deadline."},"due_time_zone":{"type":"string","description":"IANA company/deadline time zone."},"due_basis":{"type":"string","examples":["share_deadline","days_after_added","renewal","override","none"],"description":"share_deadline, days_after_added, renewal, override or none."},"completed_at":{"type":["string","null"],"format":"date-time","description":"Time Kapi’s server received the completion, including offline completions received later."},"completion_receipt_id":{"type":["string","null"],"format":"uuid","description":"Server completion receipt UUID."},"ended_at":{"type":["string","null"],"format":"date-time","description":"When the record stopped applying."},"ended_reason":{"type":["string","null"],"description":"Fixed reason code, never typed text; source_removed when the person was erased."},"waived_at":{"type":["string","null"],"format":"date-time","description":"When an administrator changed the obligation."},"waiver_actor_id":{"type":["string","null"],"format":"uuid","description":"Administrator UUID."},"waiver_reason_code":{"type":["string","null"],"description":"Fixed waiver reason code."},"outcome":{"type":"string","examples":["completed_on_time","completed_late","completed_before_requirement","not_due_yet","overdue","missed","excused","waived","optional_not_done"],"description":"completed_on_time, completed_late, completed_before_requirement, not_due_yet, overdue, missed, excused, waived or optional_not_done."},"days_late":{"type":["integer","null"],"description":"Ceiling of elapsed days past the deadline at completion or the end of the period/obligation."},"reminders_sent":{"type":"integer","minimum":0,"description":"Number of completed reminder sends known at the pinned capture."},"share_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"JSON array of all contributing share UUIDs."},"doors":{"type":"array","items":{"type":"string"},"description":"JSON array of audience doors; includes employee."},"certificate_id":{"type":["string","null"],"format":"uuid","description":"Certificate UUID."},"obligation_change_id":{"type":["integer","null"],"description":"Source obligation-change ID."}}},"Certificate":{"type":"object","required":["certificate_id","learner_profile_id","status"],"properties":{"deployment_id":{"type":"string","format":"uuid","description":"Kapi deployment UUID."},"organization_id":{"type":"string","format":"uuid","description":"Company UUID; the tenant boundary."},"learning_space_id":{"type":"string","format":"uuid","description":"Learning-space UUID."},"certificate_id":{"type":"string","format":"uuid","description":"Certificate UUID."},"learner_profile_id":{"type":"string","format":"uuid","description":"Employee profile UUID; retained after erasure."},"person_name":{"type":["string","null"],"description":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then."},"course_id":{"type":"string","format":"uuid","description":"Course UUID."},"course_release_id":{"type":["string","null"],"format":"uuid","description":"Immutable published course version UUID."},"course_release_version":{"type":["integer","null"],"description":"Published course version number."},"certificate_title":{"type":"string","description":"Title recorded on the certificate edition."},"kind":{"type":"string","examples":["completion","assessed"],"description":"completion or assessed."},"earned_at":{"type":"string","format":"date-time","description":"Completion or assessment time."},"issued_at":{"type":"string","format":"date-time","description":"Certificate issue time."},"valid_until":{"type":["string","null"],"format":"date-time","description":"Fixed expiry instant; null means no expiry."},"status":{"type":"string","examples":["valid","expired","revoked","replaced"],"description":"Certificate state at the requested moment: valid, expired, revoked or replaced."},"changed_at":{"type":["string","null"],"format":"date-time","description":"Revocation/replacement time, when known at this snapshot."},"changed_by":{"type":["string","null"],"format":"uuid","description":"Actor UUID for revocation/replacement."},"supersedes_id":{"type":["string","null"],"format":"uuid","description":"Earlier certificate corrected by this certificate."},"renews_id":{"type":["string","null"],"format":"uuid","description":"Earlier certificate renewed by this certificate."},"check_code":{"type":"string","x-required-scope":"people:read","description":"Certificate check code. Returned only with people:read because it opens the public check page, which shows the holder's name. The check page shows nothing once the holder is erased."},"live_session_id":{"type":["string","null"],"format":"uuid","description":"Live session UUID when the person finished the course by attending a live session in full; null otherwise."},"live_session_format":{"type":["string","null"],"examples":["online","on_site"],"description":"online or on_site."},"live_session_starts_at":{"type":["string","null"],"format":"date-time","description":"When the live session started."},"live_session_ends_at":{"type":["string","null"],"format":"date-time","description":"When the live session ended."},"live_session_minutes":{"type":["integer","null"],"description":"Length of the live session in minutes."},"live_session_marked_by":{"type":["string","null"],"examples":["trainer","kapi"],"description":"Who made the final register mark: trainer, or kapi when Kapi changed it after an ask."},"trainer_name":{"type":["string","null"],"x-required-scope":"people:read","description":"Trainer of the live session, from their current Kapi profile; A Kapi expert until they set a name."}}},"Person":{"type":"object","required":["person_id","entry_type","access_state"],"properties":{"deployment_id":{"type":"string","format":"uuid","description":"Kapi deployment UUID."},"organization_id":{"type":"string","format":"uuid","description":"Company UUID; the tenant boundary."},"learning_space_id":{"type":"string","format":"uuid","description":"Learning-space UUID."},"sequence":{"type":"integer","minimum":0,"description":"Sequence of this stored row version in the company history; snapshot.sequence is the upper boundary for the pull."},"valid_from":{"type":"string","format":"date-time","description":"When these facts took effect."},"recorded_at":{"type":"string","format":"date-time","description":"When the history writer captured them."},"cause":{"type":"string","examples":["first","change","sweep"],"description":"first, change or sweep."},"logic_version":{"type":"integer","minimum":0,"description":"Version of the shared owed-learning decider."},"person_id":{"type":"string","format":"uuid","description":"Stable employee or invitation row UUID."},"learner_profile_id":{"type":["string","null"],"format":"uuid","description":"Employee profile UUID; retained after erasure."},"invitation_id":{"type":["string","null"],"format":"uuid","description":"Invitation UUID for invitation rows."},"entry_type":{"type":"string","examples":["employee","invitation"],"description":"employee or invitation; accepted invitations and employees are distinct source rows."},"person_name":{"type":["string","null"],"description":"Name from its current source row. Erased person once Kapi erased the person at the company’s instruction. Certificate names are the issued subject name until then."},"work_email":{"type":["string","null"],"description":"Address on the current source employee invitation; not the Auth account email. Null for an erased person."},"invited_at":{"type":["string","null"],"format":"date-time","description":"Invitation creation time."},"joined_at":{"type":["string","null"],"format":"date-time","description":"When the person joined Kapi; not an employment start date."},"left_at":{"type":["string","null"],"format":"date-time","description":"When access ended in Kapi."},"invitation_expires_at":{"type":["string","null"],"format":"date-time","description":"Invitation expiry time."},"invitation_state":{"type":["string","null"],"description":"Stored invitation state."},"invitation_status":{"type":["string","null"],"description":"Invitation state at the requested moment, including expiry."},"access_state":{"type":"string","description":"active, suspended, ended, erased, invited or staff_practice. erased means Kapi removed the person at the company’s instruction."},"team_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"JSON array of company team UUIDs."},"doors":{"type":"array","items":{"type":"string"},"description":"JSON array of audience doors; includes employee."},"records_owed":{"type":"integer","minimum":0,"description":"Open required employee records at the requested moment."},"records_completed":{"type":"integer","minimum":0,"description":"Completed employee records at the requested moment."},"no_required_records":{"type":"boolean","description":"True when no required records are open."}}},"Course":{"type":"object","required":["row_id","course_id","source_kind"],"properties":{"row_id":{"type":"string","format":"uuid","description":"Stable course release row UUID."},"course_id":{"type":"string","format":"uuid","description":"Course UUID."},"course_release_id":{"type":["string","null"],"format":"uuid","description":"Immutable published course version UUID."},"course_title":{"type":["string","null"],"description":"Title from the current source course."},"version":{"type":["integer","null"],"description":"Published version number."},"published_at":{"type":["string","null"],"format":"date-time","description":"Time the release was sealed."},"publisher_id":{"type":["string","null"],"format":"uuid","description":"Publisher UUID."},"content_sha256":{"type":["string","null"],"description":"Immutable course content fingerprint."},"contents_sha256":{"type":["string","null"],"description":"Fingerprint of the list of contained releases."},"source_kind":{"type":"string","examples":["course_release","lesson_share_without_course_release","release_unavailable"],"description":"course_release, release_unavailable or lesson_share_without_course_release."},"lessons":{"type":"array","items":{"type":"object","properties":{"release_id":{"type":"string","format":"uuid"},"title":{"type":["string","null"]},"version":{"type":"integer","minimum":0},"content_sha256":{"type":["string","null"]}}},"description":"JSON array of lesson release IDs, current source titles, versions and content SHA-256 values."}}}}}}