> ## Documentation Index
> Fetch the complete documentation index at: https://doc.quippy-lab.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Assign a test

> List tests, create an assignment, and send your user into the exam.

This is the end-to-end flow. All calls are server-to-server with your
[API key](/partner-api/authentication).

## 1. List available tests

```bash theme={null}
curl https://api-sg.quippy-lab.com/api/partner/v1/tests \
  -H "Authorization: Bearer qk_live_..."
```

```json theme={null}
{
  "data": [
    {
      "testId": "exm_01...",
      "title": "English Entry — Sample A",
      "description": "Entry assessment, four skills",
      "durationMinutes": 60,
      "proctoring": "none",
      "marking": "manual",
      "resultVisibility": "after_review"
    }
  ],
  "total": 1,
  "limit": 20,
  "offset": 0
}
```

Only tests **published to your institution** are returned — we curate this set
with you. `resultVisibility` tells you in advance which completion experience
the student will get (see [step 4](#4-the-student-finishes)).

Paginate with `?limit=` (1–100, default 20) and `?offset=`.

## 2. Create an assignment

```bash theme={null}
curl -X POST https://api-sg.quippy-lab.com/api/partner/v1/assignments \
  -H "Authorization: Bearer qk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7c9e-..." \
  -d '{
    "externalUserId": "your-sub-123",
    "testId": "exm_01...",
    "user": { "email": "student@example.com", "name": "Jordan Lee" },
    "callbackUrl": "https://portal.example.com/return",
    "metadata": { "cohort": "spring-2026" }
  }'
```

| Field            | Required | Notes                                                                                      |
| ---------------- | -------- | ------------------------------------------------------------------------------------------ |
| `externalUserId` | yes      | Your stable user id (≤128 chars). Identity is keyed on this, not email.                    |
| `testId`         | yes      | From step 1.                                                                               |
| `user.email`     | yes      | For display and notifications. Email may change on your side without breaking the mapping. |
| `user.name`      | no       | Display name.                                                                              |
| `callbackUrl`    | yes      | HTTPS, on your [allowlisted domain](/partner-api/authentication).                          |
| `dueAt`          | no       | ISO 8601. The assignment expires if not started by then.                                   |
| `metadata`       | no       | Opaque JSON object (≤1 KB), echoed on webhooks and status reads.                           |

Response:

```json theme={null}
{
  "assignmentId": "pas_01...",
  "status": "assigned",
  "externalUserId": "your-sub-123",
  "testId": "exm_01...",
  "startUrl": "https://procto.quippy-lab.com/verify?examId=exm_01...#token=...",
  "startUrlExpiresAt": "2026-07-30T00:00:00Z",
  "createdAt": "2026-07-23T08:30:00Z"
}
```

We match or create the Quippy user for your `externalUserId` automatically —
no pre-provisioning needed.

<Tip>
  Send an **`Idempotency-Key`** header (any unique string) so a retried request
  after a network blip returns the same assignment instead of creating a
  duplicate. Reusing a key with a different body returns `409`.
</Tip>

## 3. Send the student in

Render `startUrl` as your **"Start test"** button. When the student clicks it,
they're signed in automatically and dropped directly onto the assigned test —
no password, no separate Quippy login.

* The link is valid until `startUrlExpiresAt` (default 7 days, never past
  `dueAt`). Fetch a fresh one any time from
  [`GET /assignments/{id}`](/partner-api/reference#get-an-assignment).
* Re-opening it re-enters an in-progress attempt. It does **not** allow a
  retake after submission — create a new assignment for that.

## 4. The student finishes

The completion experience depends on the test's `resultVisibility`:

| `resultVisibility` | Student sees                   | Then                          |
| ------------------ | ------------------------------ | ----------------------------- |
| `immediate`        | Their result                   | **"Return to \<you>"** button |
| `after_review`     | A "marking in progress" screen | **"Return to \<you>"** button |

The return button sends them to your `callbackUrl` with correlation params
appended:

```
https://portal.example.com/return?partnerAssignmentId=pas_01...&externalUserId=your-sub-123&status=submitted
```

<Warning>
  Treat the return redirect as **navigation only** — users close tabs and
  links get replayed. Your source of truth for assignment state is
  [webhooks](#5-receive-results) and `GET /assignments/{id}`, never the
  redirect.
</Warning>

## 5. Receive results

Subscribe a [webhook endpoint](/webhooks/setup) to get real-time updates.
Every event for a Partner API assignment carries your `externalUserId`,
`partnerAssignmentId`, and `metadata` echo — [see the event
catalog](/webhooks/events).

| Event                | Fires when                                              |
| -------------------- | ------------------------------------------------------- |
| `exam.started`       | The student begins the test                             |
| `violation.detected` | A critical proctoring violation (proctored tests only)  |
| `exam.completed`     | The student submits (carries the auto-marked score)     |
| `grading.done`       | Manual / AI marking finishes (for `after_review` tests) |

No webhook receiver yet? Poll
[`GET /assignments/{id}`](/partner-api/reference#get-an-assignment) — it
reports live `status` and, once available, the `result`.
