Growth LMS Integration API (Developers)

Test authentication, Attendance, map partner members, check course eligibility, and report attendance to Semis Growth LMS using our REST API.

The Semis Partner API connects external member and attendance systems to Growth LMS. Follow this guide in order: authenticate → preview a member sync → apply/review → read back → optionally check eligibility → optionally report attendance. Use it to submit attendance from instructor-led training, employee development events, or other organisational learning programmes. Stable external identifiers support consistent member matching and attendance reconciliation.

Before you start

You need an authorised organisation integration key and a server-side HTTPS client. Use an owner-approved test member and event. The public URL below is production, not a disposable sandbox. Replace all illustrative IDs, emails, and timestamps with your approved test data before writing. This reference is for integration developers. You need an authorised organisation integration key and a server-side application that can make HTTPS requests.

Base URL and authentication

https://semis.reispar.com/api/v1/partner/

The public Semis domain proxies this API to the backend. A separate backend hostname is not required. Use the full API path, not the homepage or the JWT-authenticated /api/v1/integrations/ settings endpoints. If Semis supplies a separate test environment, use that environment's hostname and key consistently.

Responses are plain JSON. Authenticate every request using the full secret (starting with sk_), not the integration ID or the abbreviated display prefix:

Authorization: Bearer <FULL_INTEGRATION_SECRET>

An Owner or Co-Owner creates a key in Organisation Settings → Integrations, selecting the required permissions. The secret is shown once at creation/rotation. A key belongs to one organisation, not one member. The key selects the organisation automatically; partner URLs do not take an organisation ID.

Protect the secret

Keep the key in a server-side secret manager. Never commit it, expose it in browser or mobile client code, or include it in screenshots and support messages. Ask an owner to rotate a leaked key. Saving a key in your app does not establish a connection: an actual authenticated request must reach Semis.

ScopeSettings permissionAllows
members:readRead membersLook up a member by external ID, list the organisation's departments, and list members not yet linked
members:writeSync membersCreate/update members and preview syncs
completions:readRead eligibilityCheck course-completion requirements
attendance:writeRecord attendanceReport attendance

Where the identifiers come from

IdentifierSourceMeaning
external_member_idYour third-party systemIts stable person ID, for example emp_5567
external_event_idYour third-party systemIts stable event ID, for example evt_1024
employee_idYour organisation/HR system, optionallyA separate field; not automatically the external member ID
Semis membership IDSemisAn internal ID; not what the partner lookup URL expects

Semis stores external_member_id on the member during sync. Creating a key does not map existing members. A manually added member may have no external ID yet; the API mapping does not require a visible column in the regular Members screen.

Use the same external member ID for sync, lookup, eligibility, and attendance. It must be unique within the organisation, including across its integrations. Do not generate a new ID for every call. URL-encode IDs in paths and query strings.

Step 1: prepare your test client

For the complete workflow, request all four scopes. For member sync only, request members:read and members:write.

The examples use Bash, with SEMIS_API_KEY already populated securely in the environment:

SEMIS_API_BASE="https://semis.reispar.com/api/v1/partner"

Postman: choose Authorization → Bearer Token and enter the full secret. For writes, choose Body → raw → JSON. Match the method and URL in each step.

Windows PowerShell: use curl.exe, not the curl alias. Bash backslash line continuations do not work in PowerShell. For example, with the secret already in $env:SEMIS_API_KEY:

curl.exe -sS -w "\nHTTP_STATUS=%{http_code}\n" "https://semis.reispar.com/api/v1/partner/members/connection-check/" -H "Authorization: Bearer $env:SEMIS_API_KEY"

Opening the URL in a browser address bar does not attach the Bearer header.

Step 2: test authentication without changing members

There is currently no dedicated connection-check endpoint. This is an ordinary member lookup using the deliberately unused external ID connection-check:

curl -sS -w '\nHTTP_STATUS=%{http_code}\n' \
  "$SEMIS_API_BASE/members/connection-check/" \
  -H "Authorization: Bearer $SEMIS_API_KEY"

If that ID does not exist, the expected result is HTTP 404:

{
  "detail": "Member not found."
}

This exact JSON 404 means authentication, organisation entitlement, and Read members permission passed; only the member lookup failed. It does not mean the key is unlinked or broken. If that external ID exists, expect 200 with its details instead.

Repeat without the header:

curl -sS -w '\nHTTP_STATUS=%{http_code}\n' \
  "$SEMIS_API_BASE/members/connection-check/"

Expected: HTTP 401:

{
  "detail": "Authentication credentials were not provided."
}

An HTML 404 or login page is not the expected partner JSON response. Check the URL, environment, and proxy configuration in that case.

Authentication is not member mapping

This test proves the key resolves to an eligible organisation, not that a member has been synced or that it is the organisation you intended. Confirm the key with its issuing owner before writing. Do not recreate a valid key because the dummy member lookup returned 404.

Step 3: preview a member mapping

Use your system's stable person ID and the person's actual membership email. Semis matches by external ID first, then by membership email inside the key's organisation.

curl -sS -w '\nHTTP_STATUS=%{http_code}\n' \
  -X POST "$SEMIS_API_BASE/members/?dry_run=true" \
  -H "Authorization: Bearer $SEMIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_member_id": "emp_5567",
    "email": "ada@example.com",
    "first_name": "Ada",
    "last_name": "Musa"
  }'

A successful preview returns 200, dry_run: true, would_block, and the proposed changes. If would_block is true, the actual sync would require review.

The preview does not apply the mapping or create a pending-review item. Request logs and Last used may still update. A preview is not a guarantee that the actual write will pass every database validation; inspect the actual response too.

Step 4: apply the mapping and resolve review

This step writes data. Repeat the same POST and JSON from step 3, using:

https://semis.reispar.com/api/v1/partner/members/

Remove ?dry_run=true and any dry_run body flag.

ResponseMeaning and next step
201, created: trueA new member was created. Confirm this was intended.
200, created: falseAn existing member was mapped/updated.
409, status: "pending_review", with review_idAn Owner, Co-Owner, or Admin must review it in Integrations → Sync activity.
Other 400 or 409Inspect validation/conflict details. Not every 409 is a review request.

An email is required to create a new member. Matching a protected existing account only by email can require approval to prevent silently taking over that record. Do not repeatedly resend a blocked request. A pending or rejected review is not a completed mapping.

Example applied response:

{
  "external_member_id": "emp_5567",
  "email": "ada@example.com",
  "first_name": "Ada",
  "last_name": "Musa",
  "employee_id": null,
  "job_title": "",
  "departments": ["Engineering"],
  "department_details": [
    { "department_id": "5b0f6a52-3c1e-4f0e-9f57-0d6f1c2a7e11", "name": "Engineering" }
  ],
  "created": false
}

Note: job_title can be a custom field within your Semis Growth Organisation, as this particular field name is not created by default.

Step 5: read back the mapped member

After a successful write or approved review:

curl -sS -w '\nHTTP_STATUS=%{http_code}\n' \
  "$SEMIS_API_BASE/members/emp_5567/" \
  -H "Authorization: Bearer $SEMIS_API_KEY"

Expected: 200, with the correct external ID, email, and profile fields. The read response does not include the sync-only created flag.

A 404 here requires checking the exact ID, key/organisation/environment, review status, and whether the member was deleted. Do not substitute an email, employee ID, or Semis membership UUID unless that exact value was synced as the external ID.

A successful new-member sync starts Semis onboarding. When the external ID and email do not match an existing Growth membership, Semis creates the membership with status invited, reserves a licensed seat, creates a Semis invitation, and queues the invitation email after the database transaction commits. The employee is not active until they complete the normal invitation flow. Their email, first name, and last name are stored on the membership; the current registration screen does not prefill profile names from this sync.

The API response confirms that Semis accepted the sync and created the invitation. It does not prove inbox delivery. Email delivery is asynchronous and can be delayed by the configured invitation-delivery rate limit or retried after a mail-provider failure. Final invitation-delivery failures are recorded in durable trailing 15-minute batches, separately from member-sync outcome summaries. An accepted sync or queued invitation must not be treated as proof of final email delivery. Updating an already synced member does not create or send another invitation.

New members require both an active subscription and an available licensed seat. A single-member sync returns 402 when no seat is available. A batch sync continues with its other members and reports that item as no_seats; free or purchase a seat and retry only that item. Do not tell an employee that they can access Growth merely because the external system accepted their member record.

Member-sync-only integrations can stop here. Send future updates using the same external ID. Mapping does not itself enrol the person in a course, complete training, or register them for a Semis event.

Syncing the same member again

You can sync a member as often as you like. What happens depends on how Semis matches them:

You sendSemis does
The same external_member_id as beforeMatches that member and updates the fields you send: first_name, last_name, employee_id, job_title, department. Fields you leave out are kept. No new invitation is sent and no new seat is used. Returns 200, created: false.
The same external_member_id with a different emailThe email is not changed. Semis matches by your ID first and only fills an email that is empty. Ask an organisation admin to change a member's email in Semis.
A new external_member_id for a member Semis has not linked yet and who has no Semis account (for example added manually or by CSV)Matched by email, linked immediately: your ID is stored on that member. Returns 200, created: false.
A new external_member_id for a member who already has a Semis accountMatched by email but held for review (409, pending_review). After an Owner, Co-Owner or Admin approves it in Integrations → Sync activity, your ID is stored and later syncs match it directly.
A different external_member_id for a member already linked to another IDHeld for review (409, pending_review), so one person is never silently re-pointed to a different record. Their existing ID is kept until it is approved.

Once a member is linked, keep using that ID for every sync, eligibility check and attendance call. Use GET /unlinked-members/ to find members you still need to link.

Step 6: configure and test eligibility (optional)

Eligibility needs a separate mapping from your event ID to required Semis courses. Ask Semis platform staff to configure Django admin → Partner integrations → Integration Course Requirements:

  1. Choose the correct organisation.
  2. Enter your external event ID, for example evt_1024.
  3. Select the required Semis course in that organisation.
  4. Optionally set valid_within_months; unset means no expiry.
  5. Keep the rule active. Add another rule for each additional required course.

The member-sync request does not create these rules. The partner API currently has no endpoint for configuring them. Members must complete the required learning in Semis; identity mapping alone does not satisfy a requirement.

curl -sS -G -w '\nHTTP_STATUS=%{http_code}\n' \
  "$SEMIS_API_BASE/eligibility/" \
  -H "Authorization: Bearer $SEMIS_API_KEY" \
  --data-urlencode "external_member_id=emp_5567" \
  --data-urlencode "external_event_id=evt_1024"

For valid input, both eligible and ineligible decisions return 200. Inspect the body, not just the HTTP status. Example with a known member and incomplete course:

{
  "eligible": false,
  "member_found": true,
  "external_member_id": "emp_5567",
  "external_event_id": "evt_1024",
  "requirements": [
    {
      "course_id": "a7012c33-1234-4567-89ab-0123456789ab",
      "course_title": "Workplace Safety",
      "completed": false,
      "completed_at": null,
      "valid_within_months": null,
      "within_window": true,
      "met": false
    }
  ],
  "reason": "One or more course requirements are not met."
}

within_window: true without an expiry does not mean completed; use met and the overall decision. For a missing member with configured rules, member_found and eligible are false, and requirement entries contain only course_id, course_title, and completed.

No configured requirement means eligible

Current behaviour returns eligible: true and an empty requirements list when no active rule matches the event ID, even if member_found is false. An event-ID typo can therefore look like a positive decision. For a prerequisite-controlled event, require a known member and the expected non-empty requirements before permitting attendance. Test both incomplete and completed courses, not just a successful HTTP response.

Your application enforces the decision. This endpoint checks course-completion requirements, not general membership access/status. It does not automatically disable check-in in your application.

Step 7: report attendance (optional, writes data)

Requires attendance:write. Apply your admission policy first: this endpoint does not recheck eligibility, mark courses complete, or enrol members.

curl -sS -w '\nHTTP_STATUS=%{http_code}\n' \
  -X POST "$SEMIS_API_BASE/attendance/" \
  -H "Authorization: Bearer $SEMIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_event_id": "evt_1024",
    "external_member_id": "emp_5567",
    "status": "present",
    "occurred_at": "2026-09-29T09:05:00Z"
  }'

Replace the sample timestamp with the actual occurrence time.

{
  "attendance_id": "b8123d44-1234-4567-89ab-0123456789ab",
  "external_event_id": "evt_1024",
  "external_member_id": "emp_5567",
  "status": "present",
  "member_found": true,
  "created": true
}

Expect 201 for a new record. The same organisation/event/member combination returns 200, created: false, and updates the existing record rather than appending a duplicate. Resending can overwrite previous attendance values: retry with the intended complete payload.

An unknown member can still produce an attendance record with member_found: false. Resolve the mapping and resend the attendance to link it.

Endpoint reference

Sync a member

POST/api/v1/partner/members/scope: members:write
FieldTypeRequiredNotes
external_member_idstringYesStable ID in your system, maximum 255 characters
emailstringFor new membersAlso used to match existing membership records
first_namestringNo
last_namestringNo
employee_idstringNoSeparate HR identifier, maximum 50 characters; must pass validation
departmentstringNoGrowth department name. Matched to an existing department ignoring case and repeated spaces; created only if nothing matches. See Departments
department_idUUIDNoExact department ID from GET /departments/. Takes precedence over department and never creates a department
job_titlestringNo

Use ?dry_run=true or a dry_run: true body flag to preview. Inspect any warnings on a successful sync.

Names keep the convention your members already use

When the member already has a name in Semis, first_name and last_name are converted to that name's convention before they are saved, so a sync never flips what the member or an admin set:

  • Same name, different convention (JOHN vs John): the existing spelling is kept.
  • Different name (Jonathan): it is still applied, converted to the existing convention (Title Case, UPPER or lower). Mixed-case names such as McDonald are not altered when the existing name is Title Case.
  • A new member is created with the names exactly as sent.

This applies to single and batch syncs. Converted fields are recorded in the organisation activity log entry for the sync (names_converted_to_member_convention).

Departments

GET/api/v1/partner/departments/scope: members:read

Read the departments Semis already has before you sync, and send their IDs so your departments map onto Semis departments instead of creating look-alikes:

{
  "departments": [
    {
      "department_id": "5b0f6a52-3c1e-4f0e-9f57-0d6f1c2a7e11",
      "name": "Engineering",
      "description": null,
      "member_count": 42
    }
  ],
  "total": 1,
  "allow_multiple_departments": false,
  "plan_name": "PROFESSIONAL",
  "max_departments": 10,
  "remaining_departments": 9,
  "can_create_departments": true
}
  • department_id is an exact match. An unknown ID is skipped with a warnings entry; nothing is created.
  • A department name is matched ignoring case and repeated spaces (human resources reuses Human Resources). Only when nothing matches is a new department created.
  • Plan limits apply. A department created by a sync counts against the plan's department limit (max_departments, 0 means unlimited). Once can_create_departments is false, the member is still synced, the department is skipped, and warnings explains why. Members are likewise limited by licensed seats: with no free seat a new member is refused (single sync 402, batch status no_seats).
  • When allow_multiple_departments is false, a member holds one department and a sync replaces it; when true, the department is added to the member's others.
  • Departments created by a sync appear in the activity log as department.created.

Sync many members

POST/api/v1/partner/members/batch/scope: members:write

Use a non-empty array of up to 200 members:

{
  "members": [
    { "external_member_id": "emp_1", "email": "one@example.com", "first_name": "One" },
    { "external_member_id": "emp_2", "email": "two@example.com", "first_name": "Two" }
  ]
}

The payload is validated before processing. Once processing starts, member syncs are independent: a service-level failure or blocked member does not roll back the others. Inspect every result; 200 does not mean all members synced.

{
  "dry_run": false,
  "total": 2,
  "created": 1,
  "updated": 1,
  "blocked": 0,
  "failed": 0,
  "results": [
    { "index": 0, "external_member_id": "emp_1", "status": "created" },
    { "index": 1, "external_member_id": "emp_2", "status": "updated" }
  ]
}

Other per-member statuses are pending_review, no_seats and error. Resolve reviews and retry only appropriate failed items. Batch dry runs use ?dry_run=true; their created/updated counts are projections, not applied changes. Inspect each preview's would_block and changed_fields.

Read a member

GET/api/v1/partner/members/{external_member_id}/scope: members:read

Returns the mapped, non-deleted member in the key's organisation, or 404. departments lists department names and department_details the matching department_id and name pairs.

member_status is the member's last recorded status in Semis:

member_statusMeaning
not_invitedAdded but no invitation has been sent
invitedInvitation sent and not yet accepted. The invitation may have lapsed; this endpoint does not recalculate it
acceptedInvitation accepted, account not yet active
activeMember can use Semis
inactiveMember is no longer active
deactivatedMember deactivated their own account in this organisation
bannedMember is banned from this organisation

The same member_status field is also returned by POST /api/v1/partner/members/ (single sync).

department_id values you send are references to existing departments only. They are never created or changed by the partner API. An unknown department_id is skipped with a warnings entry, and the member is not assigned to a department.

Members not yet linked

GET/api/v1/partner/unlinked-members/scope: members:read

Lists the organisation's members who have no external_member_id yet, so you can see whose accounts are not linked to your attendance app. Attendance for an unlinked member is stored with member_found: false and cannot be matched to them. Link a member by syncing them (matched by email) with your own ID. See Syncing the same member again.

Query parameterNotes
search3–255 characters; matches email, first or last name, or employee ID
statusnot_invited, invited, accepted, active, inactive, deactivated or banned
roleOWNER, CO_OWNER, ADMIN, MODERATOR or MEMBER
department_idA department_id from GET /departments/
cursorOpaque cursor returned as next_cursor. Omit it for the first page; do not construct or alter it. Cursors expire after 24 hours.
page_sizeDefault 50, maximum 200
{
  "results": [
    {
      "membership_id": "0b6f5f7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "email": "ada@example.com",
      "first_name": "Ada",
      "last_name": "Musa",
      "employee_id": "E77",
      "role": "MEMBER",
      "member_status": "active",
      "has_account": true,
      "departments": [
        { "department_id": "5b0f6a52-3c1e-4f0e-9f57-0d6f1c2a7e11", "name": "Engineering" }
      ]
    }
  ],
  "page_size": 50,
  "has_next": false,
  "next_cursor": null
}

Results are ordered by email and then membership ID. Pass next_cursor to retrieve the next page; it is deliberately opaque so the list remains fast for very large organisations. Removed members are never listed. An invalid filter, cursor, or page size returns 400 with a detail message. has_account: true means the person has signed up to Semis, so their first link by email is held for review. Owners and admins see the same list under Integrations → Members not yet linked.

Check eligibility

GET/api/v1/partner/eligibility/scope: completions:read

Both external_member_id and external_event_id query parameters are required. See step 6 for prerequisite configuration, decision fields, and no-rule behaviour.

Record attendance

POST/api/v1/partner/attendance/scope: attendance:write
FieldTypeRequiredNotes
external_event_idstringYesEvent ID in your system, maximum 255 characters
external_member_idstringYesMember ID in your system, maximum 255 characters
statusstringNopresent (default), absent, late, or excused
occurred_atISO 8601 stringNoActual attendance time
raw_payloadJSONNoExtra reference data; send only what is necessary and authorised

How to see whether requests reached Semis

Organisation owners and admins

In Organisation Settings → Integrations, fully reload the page after testing:

  • Last used tracks successful key authentication and organisation/plan checks, not successful member sync. It normally updates even if the later response is a member-not-found 404, validation error, missing-scope 403, or rate limit.
  • Missing/invalid-key attempts do not update Last used. Timestamp tracking is best-effort; a tracking failure is logged without blocking the request.
  • Last used is recorded at most once per minute per key. A call made within a minute of the displayed time may not change it.
  • The key list does not poll for partner traffic, so an already-open page can be stale.
  • Sync activity shows member-sync history/review, not every API call. A read, eligibility check, or dry run does not create an applied member-sync entry.
  • Member sync notification outcomes are stored durably in the database in fixed five-minute windows, rather than using cache-based batches or scheduling a task for each request. Admins receive one summary (created, updated, needing review, failed) per window instead of one notification per member or request. Celery beat retries due windows every 60 seconds; summaries are persisted atomically and idempotently so retries do not create duplicate stored summaries.
  • Successful and blocked sync counts are recorded inside the sync transaction. A database failure cannot commit the sync while silently losing its durable batch; the transaction rolls back instead. WebSocket refresh is explicitly best-effort after commit, separate from the durable outcome and summary.
  • Summaries typically arrive within six minutes when workers and beat are available. This is not an exact five-minute delivery guarantee. After an outage, pending windows are processed when service resumes, so notifications can be delayed. There is no notification for every API read.
  • API usage on each key shows its last 800 calls (succeeded, client errors, rate limited, server errors, busiest endpoints) with the time of the most recent call.
  • Sync activity is paged and can be filtered by status, so held syncs beyond the first page can be found and reviewed. Search matches contained text in email, name, or external ID. A nonempty search must contain 3 to 255 characters after trimming surrounding whitespace. Shorter or longer searches show a validation message without changing the current results or sending an invalid search. Clear the search to remove the text filter. Page sizes remain 10, 20 (default), 50, or 100.
  • Pending cards show the recorded before data, attempted request, and a field-by-field proposal for inspection before approval or rejection. These are recorded request values, not a guarantee of the current member state; approval validates the request and resolves department names/IDs.
  • Reviews are available only in Settings → Integrations, including member history, not in the member table. Bulk selection includes only visible pending rows. Changing organisation, history, filters, search, page, page size, or refreshing the activity clears selection. Bulk review requires an explicit confirmation summary; check both the success count and any per-request failures.
  • The authenticated admin bulk-review request sends only snapshot_ids (a nonempty list of at most 200 snapshot UUIDs) and action (approve or reject). Member IDs are not accepted. The response preserves action, succeeded, and failed entries (snapshot_id, member, error). It also includes requested, the unique snapshot count, and results, with each snapshot_id, a status of succeeded or failed, and optional member and error fields. The UI continues to use the preserved summary fields; it does not depend on requested or results. There is no members_without_pending_sync field.
  • Review and undo refresh affected member-table and department caches as well as sync activity. API-key usage still requires a page reload to see partner traffic.

Rate limits and retries

Response headerMeaning
X-RateLimit-LimitAllowed requests in the reported window
X-RateLimit-RemainingRemaining requests in that window
X-RateLimit-ResetSeconds until the oldest counted request expires from the minute window; not a Unix timestamp

Rate-limit headers are included when the limiter has populated its state. On 429, respect Retry-After and the JSON retry_after value when provided. Limits are per integration key, not per employee or IP address. Read, write, and batch minute allowances are separate; writes and batches also share a rolling 24-hour allowance. Applicable quotas are checked and consumed atomically.

AllowanceProfessionalEnterprise
Reads per rolling minute120600
Single-member sync / attendance writes per rolling minute60300
Batch requests per rolling minute30120
Writes + batches per rolling 24 hours20,000200,000

If the limiter is unavailable, Semis rejects the call with 503 rather than allowing an unmetered operation. Respect Retry-After. A response explicitly reporting limiter unavailability means the partner operation was not run; do not infer that from an unrelated proxy/server 503.

Use bounded retries and backoff for transient failures. Do not automatically repeat authentication errors, invalid payloads, or pending-review syncs.

Use the batch endpoint for imports instead of sending hundreds of individual requests. Attendance is an upsert, not an append-only check-in history.

Errors and troubleshooting

Status/resultInterpretation
400Missing/malformed fields; inspect field errors or detail/errors
401Missing/invalid/revoked key, inactive organisation, or failed plan entitlement
403Authenticated key lacks the endpoint's required scope
JSON 404 with Member not found.Authenticated lookup, but no matching non-deleted member in that organisation
409 with pending_reviewWait for an authorised reviewer; preserve the review_id
Other 409A data conflict; inspect the message
429Rate limit reached; wait before retrying
503 reporting an unavailable limiterRequest blocked before the partner operation; respect Retry-After and retry with backoff
HTML page or frontend 404Verify the API URL and deployed routing
5xxServer/proxy failure; retain timestamp/status and contact Semis if persistent

Serializer validation errors can be field-keyed rather than having a single detail. For example:

{
  "external_member_id": ["This field is required."]
}

Go-live test checklist

TestExpected result
Read without a key401
Read using a valid key, permitted scope, and dummy IDJSON 404, not HTML
Same read using a key without members:read403
Dry-run member syncPreview only; no mapping applied
Actual approved sync, then lookup200 lookup with the correct person
Protected email match409 pending review; approve/reject before relying on mapping
Incomplete required courseHTTP 200, eligible: false
All required courses completed within configured validityHTTP 200, eligible: true, expected requirements present
Unknown member with configured requirementseligible: false, member_found: false
Unknown/unconfigured event IDEmpty requirements; do not treat it as completed prerequisite training
First attendance report, then identical retry201 then 200; same attendance record
Attendance for an unmapped IDmember_found: false; sync and resend to reconcile
Refreshed integrations screen / staff request logsAuthentication usage and request outcome visible unless tracking failed

Need a key, an approved test environment, course-requirement configuration, or help with a failed test? Contact your organisation owner or the Semis team.