> ## Documentation Index
> Fetch the complete documentation index at: https://docs.taptalent.io/llms.txt
> Use this file to discover all available pages before exploring further.

# NOVA Vendors

> Vendor-scoped API for NOVA verification vendors—read assigned cases, upload the reports each package requires, review candidate documents, submit results, and record appointment attendance

The NOVA Vendors API lets a verification vendor (medical clinic, background-check provider, drug-test lab, or any other vendor a company works with in NOVA) connect its own system to the cases a company assigns to it. A vendor can:

* list the **cases** assigned to it and read the candidate data the company chose to share,
* upload the **reports** each case's package requires (for example a PME report and a drug test report),
* review the candidate documents shared with it and **complete** the case with a result,
* read the candidates' **appointments** and record **attendance**.

All requests use the base path `/v1/partner/nova-vendors`.

| Environment | Base URL |
| - | - |
| Sandbox | `https://sandbox.partner-api.taptalent.io/v1/partner/nova-vendors` |
| Production | `https://partner-api.taptalent.io/v1/partner/nova-vendors` |

## Authentication

Every request needs a **vendor API key**:

```
Authorization: Bearer vk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Vendor keys are different from company API keys:

* A **Company Admin** issues them to one vendor from **NOVA → Vendors → (vendor) → API access**. The key is shown once—store it in a secrets manager.
* A vendor key only works on `/v1/partner/nova-vendors`. Company API keys are rejected here, and vendor keys are rejected on every other Partner API endpoint.
* A vendor key only sees the cases assigned to that vendor in that company.
* Company Admins can **rotate** a key (the old one stops working immediately) or **revoke** it at any time. Disabling the vendor in NOVA also blocks its keys until the vendor is re-enabled.
* Sandbox keys start with `vk_test_`, production keys with `vk_live_`.

| Status | `code` | Meaning |
| - | - | - |
| 401 | `API_KEY_REQUIRED` | No `Authorization: Bearer` header. |
| 401 | `INVALID_API_KEY` | Unknown, revoked, or non-vendor key. |
| 403 | `VENDOR_INACTIVE` | The vendor is disabled in NOVA. |
| 403 | `ACCOUNT_INACTIVE` | The company's subscription is inactive. |

## Concepts

| Term | Meaning |
| - | - |
| **Case** | One verification a company assigned to you for one candidate (`caseId`). Its `checkType` and **package** come from the company's setup. |
| **Package** | Defines what you deliver for the case. `package.requiredDocuments` lists the report types (`documentTypeId` + `name`) you must upload. |
| **Shared candidate data** | Only what the company allowed for your vendor account: contact details (name, email, phone) plus the allowed default fields, custom fields, form sections, and candidate documents. Anything not allowed is never returned. |
| **Report** | A file you upload to a case. Reports are PDFs or images (max 10 MB) and are OCR-read against the report type's fields. |
| **Case status** | `Assigned`, `In Progress`, `Awaiting Response`, `Escalated`, `Reopened`, `Completed`, `Failed`. `Completed` and `Failed` cases are read-only (`readOnly: true`). |
| **File URLs** | `downloadUrl` and `uploadUrl` values are pre-signed and short-lived (15 and 5 minutes). Store IDs, not URLs. |
| **`onboardingCandidateId`** | The candidate's ID in the company's onboarding; use it to match cases with appointments. |

***

## Get Vendor

Check which vendor your key belongs to.

### `GET /nova-vendors/me`

```json theme={null}
{ "status": "success", "data": { "vendorId": 12, "name": "Calyx", "serviceType": "Medical" } }
```

***

## List Cases

### `GET /nova-vendors/cases`

| Parameter | Type | Required | Description |
| - | - | - | - |
| `status` | string | No | `open` (default), `completed`, or `all`. |
| `updatedSince` | number \| string | No | Only cases changed at or after this time (epoch ms or ISO-8601). Use it for incremental polling. |
| `onboardingCandidateId` | number | No | Cases for one candidate. |
| `page` | number | No | Page number (default: 1). |
| `limit` | number | No | Results per page (default: 20, max: 100). |

### Example Request

```bash theme={null}
curl -X GET "https://sandbox.partner-api.taptalent.io/v1/partner/nova-vendors/cases?status=open&updatedSince=2026-10-01T00:00:00Z" \
  -H "Authorization: Bearer YOUR_VENDOR_API_KEY"
```

### Example Response

```json theme={null}
{
  "status": "success",
  "data": {
    "cases": [
      {
        "caseId": 55,
        "caseDisplayId": "MED-2026-00055",
        "onboardingCandidateId": 900,
        "checkType": "Medical Test",
        "status": "Assigned",
        "resultOutcome": null,
        "readOnly": false,
        "assignedAt": 1790000000000,
        "dueAt": 1790172800000,
        "completedAt": null,
        "updatedAt": 1790000000000,
        "candidate": { "fullName": "Ana Cruz", "email": "ana.cruz@example.com", "phone": "+639171234567" },
        "package": { "packageId": 3, "name": "PME with urinalysis" }
      }
    ],
    "pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
  }
}
```

***

## Get Case

Returns the case, the candidate data shared with you, the candidate documents to review, the package's required reports, and the reports you uploaded. Opening a case records your access; an `Assigned` case moves to `In Progress` once you start working on it (on a later read or when you upload a report).

### `GET /nova-vendors/cases/:caseId`

### Example Response

```json theme={null}
{
  "status": "success",
  "data": {
    "caseId": 55,
    "caseDisplayId": "MED-2026-00055",
    "onboardingCandidateId": 900,
    "checkType": "Medical Test",
    "status": "In Progress",
    "resultOutcome": null,
    "readOnly": false,
    "assignedAt": 1790000000000,
    "dueAt": 1790172800000,
    "completedAt": null,
    "updatedAt": 1790003600000,
    "candidate": {
      "fullName": "Ana Cruz",
      "email": "ana.cruz@example.com",
      "phone": "+639171234567",
      "defaultFields": [{ "fieldKey": "dateOfBirth", "label": "Date of birth", "value": "1999-01-02" }],
      "customFields": [{ "fieldId": 7, "label": "Sex at birth", "value": "Female" }],
      "formSections": [
        {
          "key": "personal_information",
          "title": "Personal information",
          "fields": [{ "fieldId": 3, "label": "Middle name", "type": "scalar", "value": "Bautista" }]
        }
      ]
    },
    "candidateDocuments": [
      {
        "fieldId": 31,
        "label": "Government ID",
        "fileName": "passport.pdf",
        "selectedOption": "Passport",
        "documentTypeId": 4,
        "mimeType": "application/pdf",
        "uploadedAt": 1789990000000,
        "reviewStatus": "pending",
        "reviewFeedback": null,
        "reviewedAt": null,
        "downloadUrl": "https://...signed..."
      }
    ],
    "documentReviewSummary": { "total": 1, "reviewedCount": 0, "pendingCount": 1, "allApproved": false, "needsResubmit": false },
    "package": {
      "packageId": 3,
      "name": "PME with urinalysis",
      "checksIncluded": ["Medical Examination"],
      "slaHours": 48,
      "instructions": "Candidate must bring a valid ID.",
      "requiredDocuments": [
        { "documentTypeId": 41, "name": "PME report", "uploaded": false, "missingFields": ["Fitness result"] },
        { "documentTypeId": 42, "name": "Drug test report", "uploaded": false, "missingFields": [] }
      ]
    },
    "reports": []
  }
}
```

| Field | Description |
| - | - |
| `candidate.defaultFields` / `customFields` / `formSections` | Only the fields the company allowed for your vendor account; empty when none are allowed. |
| `candidateDocuments[]` | Candidate documents shared with you. Review each one before completing the case. |
| `package.requiredDocuments[]` | Report types to upload. `uploaded` turns `true` once a report of that type is attached; `missingFields` lists required fields not yet found by OCR in your reports. |
| `reports[]` | Reports you uploaded (same shape as [List Reports](#list-reports)). |

***

## Upload a Report

Uploading is two calls: get a pre-signed upload URL, `PUT` the file to it, then attach it to the case.

### 1. `POST /nova-vendors/cases/:caseId/reports/upload-url`

| Field | Type | Required | Description |
| - | - | - | - |
| `fileName` | string | Yes | Original file name. |
| `contentType` | string | Yes | `application/pdf`, `image/jpeg`, `image/png`, `application/msword`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, or `application/zip`. |
| `size` | number | No | File size in bytes (max 10 MB). |

```json theme={null}
{
  "status": "success",
  "data": {
    "fileKey": "agentic-vendor-cases/55/vendor-api/5b1c...-pme-report.pdf",
    "uploadUrl": "https://...signed...",
    "method": "PUT",
    "headers": { "Content-Type": "application/pdf" },
    "maxBytes": 10485760,
    "expiresAt": 1790003900000
  }
}
```

### 2. Upload the file

```bash theme={null}
curl -X PUT "UPLOAD_URL" -H "Content-Type: application/pdf" --data-binary @pme-report.pdf
```

The `Content-Type` must match the one you requested. The URL expires after 5 minutes.

### 3. `POST /nova-vendors/cases/:caseId/reports`

| Field | Type | Required | Description |
| - | - | - | - |
| `fileKey` | string | Yes | The `fileKey` from step 1. |
| `documentTypeId` | number | Yes, when the package lists required documents | Which of `package.requiredDocuments` this report is. |
| `label` | string | No | Display label (max 200 characters). |

```bash theme={null}
curl -X POST "https://sandbox.partner-api.taptalent.io/v1/partner/nova-vendors/cases/55/reports" \
  -H "Authorization: Bearer YOUR_VENDOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fileKey": "agentic-vendor-cases/55/vendor-api/5b1c...-pme-report.pdf", "documentTypeId": 41, "label": "PME report" }'
```

Returns **201 Created** with the report. The report appears on the candidate in the company's NOVA workspace, and the company receives the `onboarding.report.ready` webhook.

```json theme={null}
{
  "status": "success",
  "data": {
    "reportId": "8f0c2a5e-...",
    "documentTypeId": 41,
    "fileName": "pme-report.pdf",
    "label": "PME report",
    "mimeType": "application/pdf",
    "size": 254311,
    "uploadedAt": 1790004000000,
    "extractedFields": [{ "key": "fitness_result", "label": "Fitness result", "value": "Fit to work" }],
    "downloadUrl": "https://...signed..."
  }
}
```

| Status | `code` | Meaning |
| - | - | - |
| 400 | `INVALID_FILE_TYPE` / `FILE_TOO_LARGE` | Type not allowed or over 10 MB. |
| 400 | `INVALID_FILE_KEY` | The key was not issued for this case. |
| 400 | `FILE_NOT_UPLOADED` | Nothing was uploaded to the key yet. |
| 400 | `INVALID_DOCUMENT_TYPE` | `documentTypeId` missing or not in the package (`details.allowedDocumentTypeIds`). |
| 409 | `ALREADY_ATTACHED` | The file is already attached. |
| 409 | `CASE_READ_ONLY` | The case is completed or failed. |
| 422 | `INVALID_FILE` / `INVALID_FILE_CONTENT` | The stored file's type, size, or content does not match; the file is discarded. |

***

## List Reports

### `GET /nova-vendors/cases/:caseId/reports`

Returns `data.reports[]` with fresh `downloadUrl` values.

## Delete a Report

### `DELETE /nova-vendors/cases/:caseId/reports/:reportId`

Removes a report you uploaded by mistake. Not allowed on read-only cases.

***

## Review a Candidate Document

Every shared candidate document must be reviewed before the case can be completed.

### `PATCH /nova-vendors/cases/:caseId/candidate-documents/:fieldId/review`

| Field | Type | Required | Description |
| - | - | - | - |
| `reviewStatus` | string | Yes | `approved`, `needs_resubmission` (asks the candidate to re-upload), or `pending` (undo a review). |
| `feedback` | string | For `needs_resubmission` | Message shown to the candidate (max 2000 characters). |

Returns the updated document (same shape as `candidateDocuments[]`).

***

## Complete a Case

Submit your result. The required reports must be uploaded and every candidate document reviewed.

### `POST /nova-vendors/cases/:caseId/complete`

| Field | Type | Required | Description |
| - | - | - | - |
| `resultOutcome` | string | Yes | `Passed` (case `Completed`), `Failed` (case `Failed`), or `Requires Clarification` (case `Awaiting Response`). |
| `notes` | string | No | Result notes (max 2000 characters). |

Returns the case (same shape as [Get Case](#get-case)).

| Status | `code` | Meaning |
| - | - | - |
| 422 | `DOCUMENTS_PENDING_REVIEW` | Some candidate documents are not reviewed yet. |
| 422 | `VENDOR_OCR_REQUIRED_FIELDS_MISSING` | Required report fields were not found in the uploaded reports. |
| 409 | `CASE_READ_ONLY` | The case is already completed or failed. |

***

## Appointments and Attendance

Appointments the candidates on your cases booked through the company's self-scheduling.

### `GET /nova-vendors/bookings`

| Parameter | Type | Required | Description |
| - | - | - | - |
| `onboardingCandidateId` | number | No | Appointments for one candidate on your cases. |
| `status` | string | No | `ACTIVE`, `ATTENDED`, `NOT_ATTENDED`, or `CANCELLED`. |
| `from` / `to` | number \| string | No | Appointment start time range (epoch ms or ISO-8601). |
| `page` / `limit` | number | No | Pagination (default 20, max 100). |

Each booking has the same fields as [Candidate Self-Scheduling bookings](https://docs.taptalent.io/api-reference/candidate-self-scheduling.md), plus `onboardingCandidateId`; booking-form answers are not included.

### `POST /nova-vendors/attendance-codes/:attendanceCode/check-in`

Record that the candidate arrived (or did not) when they present their attendance code.

| Field | Type | Required | Description |
| - | - | - | - |
| `status` | string | Yes | `ATTENDED` or `NOT_ATTENDED`. |
| `occurredAt` | number \| string | No | When it happened (defaults to now). |
| `method` | string | No | For example `QR_SCAN`. |
| `note` | string | No | Max 1000 characters. |

Returns **404** `NOT_FOUND` when the code is unknown or belongs to a candidate who is not on one of your cases.

***

## Integration Sequence

1. The Company Admin issues your vendor key in NOVA and assigns cases to your vendor.
2. Poll `GET /cases?updatedSince=...` (for example every 15 minutes) and read each new case with `GET /cases/:caseId`.
3. Use `GET /bookings?onboardingCandidateId=...` for appointment times; record arrivals with the attendance check-in.
4. After the evaluation, upload each report in `package.requiredDocuments` (upload-url → `PUT` → attach).
5. Review the candidate documents, then `POST /cases/:caseId/complete` with the result.

## Security Notes

* Keep vendor keys server-side; never embed them in browsers or mobile apps.
* Responses contain personal data. Store only what you need and follow the company's data-processing agreement.
* Pre-signed URLs expire quickly—download files right away and fetch a fresh URL instead of storing it.
* Every request is logged and visible to the company in its Partner API logs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.