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

# Questionnaires API

> Build questionnaire templates, add show rules, read submissions, and send questionnaire requests to vendors.

For AI agents: a documentation index is available at [https://docs.coverbase.com/llms.txt](https://docs.coverbase.com/llms.txt). This page is also available in markdown by appending .md to the URL.

These endpoints let your systems build and maintain **questionnaires** (`cbqst_...`), read the **submissions** vendors send back, and open a **request** (`cbsbpt_...`) that asks a vendor to answer one or more questionnaires.

All endpoints are org-scoped to the credential: an `ak_*` key or an [OAuth token](/api-reference/oauth.md). See [API conventions](/conventions.md) for shared behavior. Errors return `{"detail": {"code": "...", "message": "..."}}`.

| Method | Path | Permission |
| - | - | - |
| `GET` | `/v1/questionnaires` | Read questionnaires |
| `POST` | `/v1/questionnaires` | Create questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}` | Read questionnaires |
| `PATCH` | `/v1/questionnaires/{questionnaire_id}` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/clone` | Create questionnaires |
| `PATCH` | `/v1/questionnaires/{questionnaire_id}/content` | Update questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses` | Read questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}` | Read questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/status` | Read questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/autofill` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/summary/generate` | Update questionnaires |
| `PUT` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/summary` | Update questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/answer-review` | Read questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/questions/{question_id}/versions` | Read questionnaires |
| `GET` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/autofill-suggestions` | Read questionnaires |
| `GET` | `/v1/questions/{question_id}/autofill-mappings` | Read questionnaires |
| `POST` | `/v1/questions/{question_id}/autofill-mappings` | Update questionnaires |
| `DELETE` | `/v1/questions/{question_id}/autofill-mappings/{mapping_id}` | Update questionnaires (not API keys) |
| `POST` | `/v1/autofill-suggestions/{suggestion_id}/decision` | Update vendors (not API keys) |
| `POST` | `/v1/emails/drafts` | Update the record the email is about |
| `PATCH` | `/v1/emails/drafts/{email_id}` | Update the record the email is about |
| `POST` | `/v1/questionnaires/{questionnaire_id}/conditions` | Update questionnaires |
| `PATCH` | `/v1/questionnaire-conditions/{condition_id}` | Update questionnaires |
| `POST` | `/v1/questionnaire-conditions/{condition_id}/archive` | Update questionnaires (not API keys) |
| `POST` | `/v1/questionnaires/{questionnaire_id}/question-imports` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/condition-imports` | Update questionnaires |
| `GET` | `/v1/spreadsheet-import-jobs/{job_id}` | Read questionnaires |
| `POST` | `/v1/emails/preview` | Read portals |
| `POST` | `/v1/emails/suggestions` | Read portals |
| `POST` | `/v1/portals/{portal_id}/submissions/{submission_id}/request-revision` | Update portals |
| `POST` | `/v1/portals/{portal_id}/submissions/{submission_id}/accept` | Update portals (not API keys) |
| `GET` | `/v1/irq-library` | Read questionnaires |
| `POST` | `/v1/portals` | Create portals |
| `PATCH` | `/v1/portals/{portal_id}` | Update portals |
| `POST` | `/v1/portals/{portal_id}/pause` | Update portals |
| `POST` | `/v1/portals/{portal_id}/resume` | Update portals |
| `POST` | `/v1/portals/{portal_id}/invitations` | Update portals |
| `GET` | `/v1/portals/{portal_id}/submissions` | Read portals |
| `POST` | `/v1/vendor-outreach-requests/{request_id}/decision` | Update portals (not API keys) |
| `POST` | `/v1/vendors/{vendor_id}/contact-grants/revoke` | Update vendors (not API keys) |
| `GET` | `/v1/portal-templates` | Read portals |
| `GET` | `/v1/portal-templates/{portal_template_id}` | Read portals |
| `POST` | `/v1/portal-templates/{portal_template_id}/portals` | Create portals |
| `POST` | `/v1/portal-templates` | Update portals |
| `PATCH` | `/v1/portal-templates/{portal_template_id}` | Update portals |
| `POST` | `/v1/portal-templates/{portal_template_id}/publish` | Update portals |
| `POST` | `/v1/portal-templates/{portal_template_id}/rotate-link` | Update portals |
| `GET` | `/v1/portal-templates/defaults` | Read portals |
| `PUT` | `/v1/portal-templates/defaults` | Update portals |
| `GET` | `/v1/portal-templates/invitation-email-template` | Read portals |
| `GET` | `/v1/portal-templates/sending-invitation` | Read portals |
| `POST` | `/v1/portals/{portal_id}/submissions` | Update portals |
| `GET` | `/v1/portals/{portal_id}/reminder-email-template` | Read portals |
| `GET` | `/v1/portals/{portal_id}/invitation-email-template` | Read portals |
| `GET` | `/v1/portals/{portal_id}/invitation-recipients` | Update portals |
| `POST` | `/v1/portals/{portal_id}/apply-template` | Update portals |
| `POST` | `/v1/portal-directory-exposure-checks` | Update portals |
| `GET` | `/v1/portals/{portal_id}/review` | Read questionnaires |
| `POST` | `/v1/questionnaires/default-know-your-vendor` | Create questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses` | Update questionnaires |
| `PATCH` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}` | Update questionnaires (not API keys to approve, reject or archive) |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/finalize` | Update questionnaires (not API keys) |
| `PUT` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/reviewer` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/answers` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/question-notes` | Update questionnaires |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/attestation` | Update questionnaires (not API keys) |
| `POST` | `/v1/questionnaires/{questionnaire_id}/responses/{response_id}/attestation/clear` | Update questionnaires |
| `GET` | `/v1/questionnaire-responses/{response_id}/documents` | Read questionnaires and vendors |
| `GET` | `/v1/questionnaire-responses/{response_id}/diff` | Read questionnaires |
| `GET` | `/v1/vendors/{vendor_id}/questionnaire-matches` | Read questionnaires and vendors |
| `PUT` | `/v1/questions/{question_id}/option-source` | Update questionnaires |
| `DELETE` | `/v1/questions/{question_id}/option-source` | Update questionnaires |
| `GET` | `/v1/custom-fields/{custom_field_id}/autofill-questions` | Read custom fields |
| `GET` | `/v1/custom-fields/autofill-sources` | Read custom fields |
| `GET` | `/v1/question-autofill/record-fields` | Read questionnaires |
| `GET` | `/v1/intake-sessions` | Read intake requests |
| `POST` | `/v1/intake-sessions/query` | Read intake requests |
| `POST` | `/v1/intake-sessions/exports` | Export intake requests |
| `PATCH` | `/v1/intake-sessions/{id}` | Update intake requests (not API keys to accept or reject) |
| `PUT` | `/v1/intake-sessions/{id}/questionnaire-response` | Update intake requests |
| `POST` | `/v1/intake-sessions/{id}/vendor-info` | Update intake requests |
| `POST` | `/v1/intake-sessions/{id}/potential-vendors` | Update intake requests |
| `POST` | `/v1/intake-sessions/{id}/vendor-suggestions` | Update intake requests |
| `POST` | `/v1/intake-sessions/{id}/follow-up-questions` | Update intake requests |
| `POST` | `/v1/intake-sessions/{id}/process-questions` | Update intake requests |
| `GET` | `/v1/intake-sessions/{id}/prequalification` | Read vendors |
| `GET` | `/v1/intake-sessions/{id}/requester-journey` | Read intake requests |
| `GET` | `/v1/workflows/runs/{workflow_run_id}/requester-journey` | Read intake requests |
| `GET` | `/v1/intake-sessions/{id}/triage` | Read vendors |
| `PUT` | `/v1/intake-sessions/{id}/triage/identifiers` | Update intake requests, and update vendors or be the requester |
| `PUT` | `/v1/intake-sessions/{id}/triage/blocked-override` | Update vendors (not API keys) |
| `PUT` | `/v1/intake-sessions/{id}/triage/disposition` | Update vendors |
| `POST` | `/v1/intake-sessions/{id}/triage/disposition/sign-off` | Update vendors (not API keys) |
| `POST` | `/v1/intake-sessions/{id}/triage/disposition/decline` | Update vendors (not API keys) |
| `POST` | `/v1/intake-sessions/{id}/triage/questions` | Update vendors |
| `POST` | `/v1/intake-sessions/{id}/triage/questions/{question_id}/answer` | Update intake requests; the requester only |
| `POST` | `/v1/intake-sessions/{id}/triage/irqs/send` | Update vendors |
| `PUT` | `/v1/intake-sessions/{id}/triage/procurement-origin` | Update vendors |
| `PUT` | `/v1/intake-sessions/{id}/triage/budget-exception` | Update vendors (not API keys) |
| `POST` | `/v1/intake-sessions/{id}/triage/material-change/irq-refresh` | Update vendors |
| `GET` | `/v1/intake-sessions/{id}/triage/budget-lines` | Read intake requests, and read integrations or be the requester |
| `GET` | `/v1/intake-sessions/{id}/triage/requester-notice` | Read intake requests |
| `GET` | `/v1/intake-triage/config` | Read org settings |
| `PUT` | `/v1/intake-triage/config` | Update org settings |
| `GET` | `/v1/zero-form/settings` | Read integrations |
| `PUT` | `/v1/zero-form/settings` | Update integrations |
| `GET` | `/v1/zero-form/conversations` | Read integrations |
| `GET` | `/v1/zero-form/conversations/{conversation_id}` | Read integrations |

To list requests and read one request, use the [Portals API](/api-reference/portals.md). The request object these endpoints return is the same one.

## Archiving is a human decision

API keys cannot archive a questionnaire, a question, an option, a section, or a show rule. When an API key sends `is_archived` or calls an archive endpoint, the call returns `403` with code `api_key_not_allowed`. To archive, use an OAuth token for a person who holds the archive permission.

## Versions

A questionnaire version that people already answered is locked. A content change or a show rule on a locked version returns `409` with code `version_locked`. To change it, draft a new version in Coverbase, then edit the draft.

## List questionnaires

**method** `GET`

`GET /v1/questionnaires`

Returns the org's questionnaires, newest first, without their questions.

**type** `string`

Only questionnaires of this type: `irq`, `vendor_onboarding`, `assessment`, `informational`, `follow_up`, `rfp_prequalification`, `rfp_response`, `performance`, or `kys`.

**limit** `integer` (default: 50)

Page size, 1-200.

**offset** `integer` (default: 0)

Rows to skip.

The response has `items`, `total`, `limit`, and `offset`. Each item has:

**id** `string`

Questionnaire ID (`cbqst_...`).

**name** `string`

Name.

**description** `string | null`

Description.

**type** `string`

Questionnaire type.

**version\_number** `integer`

Version number.

**version\_status** `string`

`draft`, `published`, or `retired`.

**is\_archived** `boolean`

Whether it is archived.

**created\_at** `integer`

Unix timestamp (seconds).

## Create a questionnaire

**method** `POST`

`POST /v1/questionnaires`

Creates a blank questionnaire, or forks one from the IRQ library. Returns `201` with the full questionnaire. To add questions, send [`PATCH /v1/questionnaires/{questionnaire_id}/content`](#edit-questions-and-sections).

**name** `string`

Name. Required unless you send `irq_library_slug`.

**type** `string` (default: informational)

Questionnaire type.

**description** `string`

Description.

**irq\_library\_slug** `string`

Fork this inherent risk questionnaire from [`GET /v1/irq-library`](#list-the-irq-library). An invalid slug returns `422` with code `invalid_library_template`.

## Get a questionnaire

**method** `GET`

`GET /v1/questionnaires/{questionnaire_id}`

Returns the questionnaire with its live `sections`, `questions`, and `conditions`. Each question has `id`, `text`, `response_type`, `section_id`, `external_idx`, `sort_order`, `is_required`, `is_internal_only`, and `options` (each with `id`, `name`, and `value`, its score). An unknown ID returns `404` with code `questionnaire_not_found`.

## Update a questionnaire

**method** `PATCH`

`PATCH /v1/questionnaires/{questionnaire_id}`

Changes the questionnaire's settings. Fields you leave out stay as they are.

**name** `string`

New name.

**description** `string`

New description.

**reviewer\_ids** `string[]`

The users who review submissions. Replaces the list.

**is\_applied\_to\_all** `boolean`

Whether the questionnaire applies to every vendor automatically.

**applied\_to\_tag\_ids** `string[]`

Vendor tags the questionnaire applies to. Replaces the list.

**inherent\_risk\_level\_ids** `string[]`

Inherent risk levels the questionnaire applies to. Replaces the list.

**is\_archived** `boolean`

Archive or restore. Refused to API keys.

## Clone a questionnaire

**method** `POST`

`POST /v1/questionnaires/{questionnaire_id}/clone`

Copies the sections, questions, options, show rules, and reviewers into a new questionnaire of the same type. Automation settings are not copied. Returns `201`. A questionnaire type that cannot be cloned returns `422` with code `clone_not_supported`.

**name** `string` *required*

Name of the copy.

## Edit questions and sections

**method** `PATCH`

`PATCH /v1/questionnaires/{questionnaire_id}/content`

Adds or changes sections, questions, and options in one save. An item without `id` is added. An item with `id` changes only the fields you send. Items you leave out are not touched. The response is the full questionnaire.

**sections** `object[]`

Each with `id`, `name`, `description`, `external_idx`, `sort_order`, and `is_archived`.

**questions** `object[]`

Each with `id`, `text`, `response_type` (for example `free_text`, `select_one`, `select_multiple`, `date`, or `document_upload`), `section_id`, `additional_instructions`, `guidance`, `sort_order`, `external_idx`, `is_required`, `is_internal_only`, `allow_documents`, `require_documents`, `allow_comments`, `is_archived`, and `options`. A new question needs `text` and `response_type`. Each option has `id`, `name`, `description`, `value`, `sort_order`, and `is_archived`.

A change the editor would refuse returns `422` or `400` with code `content_refused`, and an invalid body returns `422` with code `invalid_content`.

## List submissions

**method** `GET`

`GET /v1/questionnaires/{questionnaire_id}/responses`

Lists the submissions to a questionnaire, newest first, without answers. Takes `limit` (1-200, default 50) and `offset`. Each item has `id`, `questionnaire_id`, `status`, `vendor_id`, `portal_id`, `assignee_id` (the reviewer), `respondent_email_address`, and `created_at`.

## Get a submission

**method** `GET`

`GET /v1/questionnaires/{questionnaire_id}/responses/{response_id}`

Returns one submission with `answers`. Each answer has `question_id`, `question_text`, `text`, `option_id`, `selected_option_ids`, `response_data`, and `comment`. Answers to internal-only questions are left out. An unknown ID returns `404` with code `response_not_found`.

## Draft answers with AI

**method** `POST`

`POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/autofill`

Starts a background run in which Coverbase drafts the submission's answers from the files uploaded to its request. Returns `202` with the submission's `status` and `autofill` progress. To follow the run, poll `GET /v1/questionnaires/{questionnaire_id}/responses/{response_id}/status` until `autofill.phase` is `done`, then read the answers with [Get a submission](#get-a-submission).

**use\_assessment\_documents** `boolean` (default: false)

Also draft from the linked assessment's documents.

**use\_web\_search** `boolean` (default: true)

Set to false to draft from the documents alone.

**document\_ids** `string[]`

Draft from these uploaded files only.

A request with no files returns `400` with code `autofill_no_documents`. A run already in progress returns `409` with code `autofill_in_progress`. A submitted response returns `400` with code `already_submitted`.

## Analysis summary

To regenerate a submission's AI analysis summary, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/summary/generate`. It returns `202`, and the new summary replaces the old one when the run finishes.

To replace the summary yourself, send `PUT /v1/questionnaires/{questionnaire_id}/responses/{response_id}/summary` with `summary`, an object in the assessment summary shape. An invalid summary returns `422` with code `invalid_summary`.

## Answer review and versions

`GET /v1/questionnaires/{questionnaire_id}/responses/{response_id}/answer-review` returns `enabled`, `carried_forward` (each answer copied from an earlier submission, with `carry_forward_state` and `carried_forward_from_id`), and `edit_reviews` (each reviewer change with `original_answer_text`, `edited_answer_text`, and the vendor's `status`). When your org does not use answer review, `enabled` is false and the lists are empty.

`GET /v1/questionnaires/{questionnaire_id}/responses/{response_id}/questions/{question_id}/versions` returns every version of one answer, including replaced ones (`is_archived` is true). A question with no answer returns `404` with code `answer_not_found`.

## What a question writes to the record

An autofill mapping says what a question's answer writes to the vendor, service, engagement, or assessment record when the submission is approved.

`GET /v1/questions/{question_id}/autofill-mappings` lists a question's mappings. Each has `id`, `mode`, `target_object`, `target_property`, `custom_field_config_id`, `record_field_name`, `instruction`, `requires_review`, `prefills_answer`, and `option_rules`.

To add a mapping, or replace the one named by `id`, send `POST /v1/questions/{question_id}/autofill-mappings`. It returns `201` with the mapping.

**target\_property** `string` *required*

`custom_field`, `record_field`, `tag`, `contact`, or `location`.

**target\_object** `string` (default: vendor)

`vendor`, `service`, `engagement`, or `assessment`.

**mode** `string` (default: fixed)

`fixed` writes by option rule or copies the answer. `ai` asks the model, and needs `instruction`.

**custom\_field\_config\_id** `string`

The custom field, for a `custom_field` target.

**record\_field\_name** `string`

The record column, for a `record_field` target.

**instruction** `string`

What the model should write, for `ai` mode.

**requires\_review** `boolean` (default: true)

Whether a reviewer confirms the change before approval writes it.

**prefills\_answer** `boolean` (default: false)

Also answer the question from the record.

**option\_rules** `object[]`

For `fixed` mode on a select question: each with `option_perma_id` and either `tag_id` or `literal_value`.

A mapping that would save and then write nothing returns `400` with code `mapping_refused` and the reason in `message`.

To remove a mapping, send `DELETE /v1/questions/{question_id}/autofill-mappings/{mapping_id}`. It returns `204`. API keys get `403` with code `api_key_not_allowed`.

`GET /v1/questionnaires/{questionnaire_id}/responses/{response_id}/autofill-suggestions` lists the changes a submission proposes, each with `id`, `target_object`, `target_property`, `values`, `rejected_values`, and `decision`.

## Decide a proposed record change

**method** `POST`

`POST /v1/autofill-suggestions/{suggestion_id}/decision`

A submission can propose changes to the vendor record, such as a tag or a contact, that approval writes. This endpoint accepts or rejects one of them before approval. API keys get `403` with code `api_key_not_allowed`; use a person's OAuth token.

**decision** `string`

`accepted`, `rejected`, or null to clear the verdict.

**value** `string`

For a contact suggestion, the one address the verdict is on.

## Preview and draft email text

To see an email as the recipient will read it, send `POST /v1/emails/preview` with `subject`, `body`, and the records whose values fill its variables (`vendor_id`, `assessment_id`, `portal_id`, `case_thread_id`, `service_ids`). Variables written as `{{namespace::path}}` are filled; any that nothing can fill stay visible. Send `as_sent: true` to also get `html`, the message as the send path builds it inside your org's email layout. A portal that does not exist returns `404` with code `portal_not_found`.

To have Coverbase's AI draft an email subject or body, send `POST /v1/emails/suggestions` with `field_name` (`subject` or `body`), `instruction`, and optionally the email so far (`subject`, `body`, `attachments`) and the `assessment_id`, `eval_ids`, or `vendor_id` it is about. It returns `field_name` and `text`. Nothing is saved or sent.

## Email drafts

To save an unsent email about a record, send `POST /v1/emails/drafts` with `object_id`, `object_type` (`vendor`, `vendor_document`, `portal`, `contract`, `task`, `questionnaire_review`, `case`, or `case_thread`), `from_address`, and optionally `to_addresses`, `cc_emails`, `bcc_emails`, `subject`, `text_body`, and `html_body`. Each address is an object with `email` and an optional `name`. It returns `201` with `id`, `status` (`draft`), `subject`, `object_id`, and `object_type`. A person reviews and sends the draft from the record's email tab. The call needs permission to update the record. An unknown type returns `422` with code `invalid_object_type`, and a record that does not exist returns `404` with code `object_not_found`.

To change a draft, send `PATCH /v1/emails/drafts/{email_id}` with the fields to change. An email that was already sent returns `409` with code `not_a_draft`.

## Import questions and show rules from a workbook

To create or update a questionnaire's questions from an `.xlsx` workbook, send it as a multipart upload (`file`, and `dry_run`) to `POST /v1/questionnaires/{questionnaire_id}/question-imports`. The import runs in the background: the call returns `202` with the job's `id` and `status`. To follow it, poll `GET /v1/spreadsheet-import-jobs/{job_id}` until `status` is `completed`, `rejected` (the workbook as a whole could not be imported), or `failed`. The finished job's `result` has a `summary` (rows to create, update, and skip) and `problems` (each skipped row and why). `dry_run` defaults to true, so send `false` to write.

To add show rules from a workbook, send the same multipart upload to `POST /v1/questionnaires/{questionnaire_id}/condition-imports`. It runs while you wait and returns the same `summary` and `problems`. Nothing is kept from a preview, or from a workbook whose every row failed. A file that is not a workbook returns `422`.

## Add a show rule

**method** `POST`

`POST /v1/questionnaires/{questionnaire_id}/conditions`

A show rule shows its targets only when an earlier answer matches. Send either the trigger fields or `clauses`. Returns `201` with the rule.

**question\_id** `string`

The trigger question.

**predicate** `string`

`one_of`, `not_one_of`, `date_after`, `date_on_or_after`, `date_before`, or `date_on_or_before`.

**reference\_values** `string[]`

Option IDs, or one Unix timestamp at UTC midnight (as a string) for a date predicate.

**clauses** `object[]`

Instead of the three fields above, up to 10 clauses (`question_id`, `predicate`, `reference_values`) that must all hold.

**target\_question\_ids** `string[]`

Questions to show.

**target\_section\_ids** `string[]`

Sections to show.

**target\_option\_ids** `string[]`

Options to show.

A rule needs at least one target. An invalid rule returns `422` with code `invalid_condition`. A rule the editor refuses, for example an internal-only trigger that controls a question the vendor sees, returns `422` with code `condition_refused`.

## Update a show rule

**method** `PATCH`

`PATCH /v1/questionnaire-conditions/{condition_id}`

Takes the same fields as [Add a show rule](#add-a-show-rule). Fields you leave out keep their value. The updated rule has a new `id`, because submissions already answered keep the old rule. An unknown ID returns `404` with code `condition_not_found`.

## Archive a show rule

**method** `POST`

`POST /v1/questionnaire-conditions/{condition_id}/archive`

Removes the rule, so its targets always show. Returns `204`. API keys get `403` with code `api_key_not_allowed`.

## List the IRQ library

**method** `GET`

`GET /v1/irq-library`

Returns the curated inherent risk questionnaires. Each has `slug`, `name`, `description`, `num_questions`, and `risk_category`.

## Open a request to a vendor

**method** `POST`

`POST /v1/portals`

Opens a request that asks one vendor to answer questionnaires, and returns `201` with the [request](/api-reference/portals.md). Coverbase adds the informational questionnaires that apply to the vendor automatically, and routes each submission to the questionnaire's reviewer or else to the credential's user. Opening a request emails nobody.

**vendor\_id** `string` *required*

Vendor (`cbvndr_...`).

**questionnaire\_ids** `string[]` *required*

Questionnaires to ask for, in order.

**type** `string` (default: informational)

`irq`, `informational`, or `vendor_onboarding`. Any other type returns `422` with code `invalid_type`.

**due\_at** `integer`

Unix timestamp the vendor should answer by.

**displayed\_message** `string`

Message the vendor sees on the request.

**service\_ids** `string[]`

Scope IRQ and informational answers to these services of the vendor.

**engagement\_id** `string`

Scope an IRQ to this engagement. Do not send it with `service_ids`.

A vendor with engagements needs `engagement_id` or `service_ids` for an IRQ, or the call returns `400` with code `request_refused`.

## Update a request

**method** `PATCH`

`PATCH /v1/portals/{portal_id}`

Changes the request's due date or the message the vendor sees on it, and returns the request. Fields you leave out stay as they are.

**due\_at** `integer`

Unix timestamp the vendor should answer by.

**displayed\_message** `string`

Message the vendor sees on the request.

## Pause and resume a request

**method** `POST`

`POST /v1/portals/{portal_id}/pause`

Puts an open request on hold. Reminders stop, and the vendor sees the request on hold. A request that is not open returns `409` with code `portal_not_open`.

**method** `POST`

`POST /v1/portals/{portal_id}/resume`

Takes the request off hold. Send `due_at` to set a new due date. Without it, the due date moves on by the time the request was held. Both return the request.

## Send the invitation

**method** `POST`

`POST /v1/portals/{portal_id}/invitations`

Emails the invitation to the vendor and grants each recipient access to the request. This contacts the vendor. Returns `201` with `email_id` and `portal_id`.

**subject** `string` *required*

Subject. Template variables resolve.

**text** `string` *required*

Body. Template variables resolve.

**to\_addresses** `string[]` *required*

Recipients.

**cc\_emails** `string[]`

Copy recipients.

**bcc\_emails** `string[]`

Blind copy recipients.

A recipient whose address bounced or unsubscribed returns `422` with code `recipient_suppressed`.

## Send an automatic collection's request

When an assessment collects documents automatically but cannot send its request to the vendor unasked, a person decides who receives it. Send `POST /v1/vendor-outreach-requests/{request_id}/decision` with `revision` (the revision you read in Coverbase), `recipients` (each with `email`, `role` as `to`, `cc`, or `bcc`, and `source`), an optional `message` (`subject`, `text`, `due_at`), and optional `recipient_consent` (each with `email` and `intent`, to save or allow an address as a vendor contact). This contacts the vendor. It returns `request_id`, `outcome`, and `skip_reason`. A request that changed since you read it returns `outcome` `skipped` with the reason. An unknown ID returns `404` with code `outreach_request_not_found`. API keys get `403` with code `api_key_not_allowed`.

To stop automatic document requests going to a vendor contact someone allowed, send `POST /v1/vendors/{vendor_id}/contact-grants/revoke` with `email`. The contact stays saved. It returns `email`, `revoked` (false when the grant was not live), and `vendor_contact`. Only the vendor's owner or an admin may revoke a grant, so API keys get `403` with code `api_key_not_allowed`.

## List submissions on a request

**method** `GET`

`GET /v1/portals/{portal_id}/submissions`

Lists the rounds the vendor submitted, newest first. Each has `id`, `status`, `submitted_at`, and `submitted_by_email_address`.

## Review a submitted round

To send a submitted round back to the vendor, send `POST /v1/portals/{portal_id}/submissions/{submission_id}/request-revision` with `revision_message`. To accept it, send `POST /v1/portals/{portal_id}/submissions/{submission_id}/accept`. API keys cannot accept a round and get `403` with code `api_key_not_allowed`. Both return the submission. A round that is not `submitted` returns `409` with code `not_submitted`. A round that carries a findings commitment returns `409` with code `commitment_round`, because you decide it on the commitment.

## Portal templates

**method** `GET`

`GET /v1/portal-templates`

Lists the org's portal templates. Filter with `portal_type`. Each has `id`, `name`, `description`, `portal_type`, `lifecycle` (`ephemeral` or `persistent`), `due_days`, `is_archived`, and `ask_specs`.

**method** `GET`

`GET /v1/portal-templates/{portal_template_id}`

Returns one template, or `404` with code `portal_template_not_found`.

**method** `POST`

`POST /v1/portal-templates/{portal_template_id}/portals`

Opens a request from an ephemeral template and returns `201` with the request. It emails nobody. The body takes `vendor_id`, `due_at`, `service_ids`, and `engagement_id`. A persistent template, or an IRQ that needs a scope, returns `422` with code `instantiate_refused`.

## Manage portal templates

To create a template, send `POST /v1/portal-templates` with `name`, `portal_type`, and any of `description`, `display_name`, `preamble`, `due_days`, `auth_config`, `ask_specs`, `reminder_config`, `invitation_email_template_id`, `default_assignee_ids`, `supplier_initiable`, and `recurrence_months`. It returns `201` with the template. A persistent template publishes its portal at once.

To change a template, send `PATCH /v1/portal-templates/{portal_template_id}` with the fields to change. Send `null` to clear `description`, `due_days`, `recurrence_months`, or `invitation_email_template_id`. A persistent template republishes its portal.

`POST /v1/portal-templates/{portal_template_id}/publish` publishes a persistent template to its one portal. Publishing again updates that portal in place, so the link stays the same. `POST /v1/portal-templates/{portal_template_id}/rotate-link` gives the portal a new link, and the old link stops working at once. An ephemeral template returns `422`. Neither call emails anyone.

`GET /v1/portal-templates/defaults` returns `default_portal_template_ids`, the org's default template for each portal type. To set one, send `PUT /v1/portal-templates/defaults` with `portal_type` and `portal_template_id`, or `null` to clear it.

`GET /v1/portal-templates/invitation-email-template` returns the invitation email a portal built from a template would send, and its `source`. Pass `portal_template_id`, or `portal_type` to read the org default for that type. `GET /v1/portal-templates/sending-invitation?email_template_id=...` lists the templates whose invitations send that email template.

## Portal settings and checks

* `POST /v1/portals/{portal_id}/submissions` starts a draft round on an open persistent portal and returns `201`. On an intake portal it also opens the intake request and assigns its reviewers. Nobody outside your org is contacted.
* `GET /v1/portals/{portal_id}/reminder-email-template` returns the portal's reminder template, or `null`. Add `inherit=true` to fall back to the template it inherits.
* `GET /v1/portals/{portal_id}/invitation-email-template` returns the email the portal's invitation sends.
* `GET /v1/portals/{portal_id}/invitation-recipients` returns the `to`, `cc`, and `bcc` an invitation would address. Pass `email_template_id` to apply that template's default copy recipients. It sends nothing.
* `POST /v1/portals/{portal_id}/apply-template` rebuilds one assessment portal from `portal_template_id`, or from the org default when you send `null`. The template and the assessment plan keep their settings.
* `POST /v1/portal-directory-exposure-checks` takes `auth_config` and `ask_configs` and returns `exposes_directory`: whether people outside your org could read your own people through that portal. It saves nothing.

## Start, answer, and decide a response

To start a response, send `POST /v1/questionnaires/{questionnaire_id}/responses` with `vendor_id`, and optionally `respondent_email_address`, `portal_id`, and first `responses`. It returns `201` and emails nobody.

To save one answer, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/answers` with `question_id` or `question_source_id`, and the answer fields (`text`, `option_id`, `selected_option_ids`, `response_data`, `comment`). A changed answer clears the closing attestation, so the respondent signs again. A questionnaire that asks for an opening attestation returns `409` until it is signed.

To leave an internal note about one question, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/question-notes` with `note` and `question_id`. It returns `204`. Vendors never see notes.

`PUT /v1/questionnaires/{questionnaire_id}/responses/{response_id}/reviewer` assigns the internal reviewer. Send `assignee_id`, a user or a user group, or `null` to clear it. The new reviewer is notified.

To approve or reject a response that is ready for review, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/finalize` with `action` (`approve` or `deny`). Approving an inherent risk questionnaire writes its tags, contacts, and inherent risk scores to the vendor. `PATCH /v1/questionnaires/{questionnaire_id}/responses/{response_id}` changes `status` or `is_archived`. API keys cannot approve, reject, archive, or unarchive a response, and get `403` with code `api_key_not_allowed`.

`GET /v1/portals/{portal_id}/review` lists the responses a portal's review covers, in the order Approve all decides them, and `review_mode`. Approving the whole set is done by a person in Coverbase or over MCP.

`GET /v1/questionnaire-responses/{response_id}/documents` lists every document attached to the response's current answers. `GET /v1/questionnaire-responses/{response_id}/diff` compares the response with an earlier one of the same questionnaire from the same vendor: `against_response_id`, or else the latest one before it. `GET /v1/vendors/{vendor_id}/questionnaire-matches` lists the informational questionnaires that apply to a vendor through its tags or inherent risk.

To create the default know-your-vendor questionnaire, send `POST /v1/questionnaires/default-know-your-vendor`. `success` is false when it could not be created.

## Attestations

To sign the opening or closing attestation on a response your own team answered, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/attestation` with `slot`, `agreed: true`, `signatory_name`, and optionally `signatory_title` and `mark`. The record names the signed-in user's email address, so API keys get `403` with code `api_key_not_allowed`. A signature the questionnaire's settings refuse returns `400`.

To ask the respondent to sign again, send `POST /v1/questionnaires/{questionnaire_id}/responses/{response_id}/attestation/clear?slot=closing`. The old record stays on the ledger. An unknown response returns `404` with code `response_not_found`.

## Options and autofill sources

`PUT /v1/questions/{question_id}/option-source` with `custom_field_config_id` makes a select question take its options from that custom field. `DELETE /v1/questions/{question_id}/option-source` stops it. The current options stay as ordinary options. An unknown question returns `404` with code `question_not_found`.

`GET /v1/custom-fields/{custom_field_id}/autofill-questions` lists the questions that fill one custom field. `GET /v1/custom-fields/autofill-sources` does the same for every mapped field. A credential without questionnaire read access sees no questions. `GET /v1/question-autofill/record-fields` lists the record columns a mapping may name. `prefill_only` marks a column a mapping may read but never write.

## Intake requests

Your systems can also open intake requests with `/v1/intake/api/sessions`. The routes below read and work the requests your org already has.

* `GET /v1/intake-sessions` lists intake requests, newest first. Filter with `statuses`, `vendor_id`, `user_id`, `submitter_scope`, and `search` (the vendor name). `POST /v1/intake-sessions/query` takes a unified filter, `search`, `sort`, `limit`, and `offset`.
* `POST /v1/intake-sessions/exports` takes the same filter and returns `download_url`, `file_name`, and `expires_in_seconds` for an Excel workbook. The link is short-lived, and the response is not cached.
* `PATCH /v1/intake-sessions/{id}` updates vendor details, `session_data`, or `status`. Setting `status` to `submitted` submits the request with the same checks as the dashboard. Accepting or rejecting a request stays a human decision: API keys get `403` with code `api_key_not_allowed` for `accepted`, `rejected`, or `partial`.
* `PUT /v1/intake-sessions/{id}/questionnaire-response` saves one draft answer and merges it onto the stored one.
* `POST /v1/intake-sessions/{id}/vendor-info` researches the vendor in `vendor_search_input`. `POST /v1/intake-sessions/{id}/potential-vendors` stores `use_case` and proposes vendors from the web. `POST /v1/intake-sessions/{id}/vendor-suggestions` suggests qualified vendors. These run AI and web search and can take a minute.
* `POST /v1/intake-sessions/{id}/follow-up-questions` generates the follow-up questions the request's questionnaires need. `POST /v1/intake-sessions/{id}/process-questions` starts drafting every answer in the background. Poll the request until `is_processing_questions` is false.
* `GET /v1/intake-sessions/{id}/prequalification` returns the prequalification standing of the request's vendor.
* `GET /v1/intake-sessions/{id}/requester-journey` and `GET /v1/workflows/runs/{workflow_run_id}/requester-journey` show where a request stands and what blocks it.

An unknown request returns `404` with code `intake_session_not_found`. The prequalification and requester journey routes return `404` with code `not_enabled` when your org does not use the third-party lifecycle.

## Front Door triage

These routes need Front Door triage. Without it they return `404` with code `not_enabled`.

* `GET /v1/intake-sessions/{id}/triage` returns the review card: duplicate and Do Not Use matches, IRQ scoping, the disposition and its sign-offs, and the reviewer's questions.
* `PUT .../triage/identifiers` sets `tax_id`, `duns_number`, and `lei`. The requester may set them too.
* `PUT .../triage/disposition` records or revises the disposition. A revision clears both sign-offs and notifies the sign-off groups. When the scoping requires IRQs, they go to the requester.
* `POST .../triage/disposition/sign-off` (with `role`) and `POST .../triage/disposition/decline` (with `reason`) are a sign-off group's decision. `PUT .../triage/blocked-override` (with `rationale`) lets a request go ahead although its vendor is Do Not Use. `PUT .../triage/budget-exception` decides a budget exception. API keys get `403` with code `api_key_not_allowed` on all four.
* `POST .../triage/questions` sends the request back to its requester with `questions`. The requester is notified in Coverbase, or emailed when they have no account. The requester answers with `POST .../triage/questions/{question_id}/answer`.
* `POST .../triage/irqs/send` sends the IRQs the scoping requires, if they have not gone yet. `PUT .../triage/procurement-origin` records the requisition the request came from. `POST .../triage/material-change/irq-refresh` opens an engagement's IRQ refresh.
* `GET .../triage/budget-lines` lists the synced budget lines the spend can map to. `GET .../triage/requester-notice` returns what the requester is told.
* `GET /v1/intake-triage/config` and `PUT /v1/intake-triage/config` read and replace your Front Door settings. They need the org settings permission, which API keys do not hold. Use a key created by a person whose role holds it.

## Zero-form intake

`GET /v1/zero-form/settings` returns which channels accept intake requests without a form, and the inbox address. `PUT /v1/zero-form/settings` turns `slack_enabled`, `teams_enabled`, `email_enabled`, and `mcp_enabled` on or off. `GET /v1/zero-form/conversations` lists the conversations requesters started, filtered by `channel`, and `GET /v1/zero-form/conversations/{conversation_id}` returns one with its messages. These routes return `404` with code `not_enabled` when your org does not use the third-party lifecycle.


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