Skip to main content
For AI agents: a documentation index is available at 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. See API conventions for shared behavior. Errors return {"detail": {"code": "...", "message": "..."}}. To list requests and read one request, use the Portals API. 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

GET
GET /v1/questionnaires
Returns the org’s questionnaires, newest first, without their questions.
string
Only questionnaires of this type: irq, vendor_onboarding, assessment, informational, follow_up, rfp_prequalification, rfp_response, performance, or kys.
integer
default:"50"
Page size, 1-200.
integer
default:"0"
Rows to skip.
The response has items, total, limit, and offset. Each item has:
string
Questionnaire ID (cbqst_...).
string
Name.
string | null
Description.
string
Questionnaire type.
integer
Version number.
string
draft, published, or retired.
boolean
Whether it is archived.
integer
Unix timestamp (seconds).

Create a questionnaire

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.
string
Name. Required unless you send irq_library_slug.
string
default:"informational"
Questionnaire type.
string
Description.
string
Fork this inherent risk questionnaire from GET /v1/irq-library. An invalid slug returns 422 with code invalid_library_template.

Get a questionnaire

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

PATCH
PATCH /v1/questionnaires/{questionnaire_id}
Changes the questionnaire’s settings. Fields you leave out stay as they are.
string
New name.
string
New description.
string[]
The users who review submissions. Replaces the list.
boolean
Whether the questionnaire applies to every vendor automatically.
string[]
Vendor tags the questionnaire applies to. Replaces the list.
string[]
Inherent risk levels the questionnaire applies to. Replaces the list.
boolean
Archive or restore. Refused to API keys.

Clone a questionnaire

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.
string
required
Name of the copy.

Edit questions and sections

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.
object[]
Each with id, name, description, external_idx, sort_order, and is_archived.
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

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

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

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.
boolean
default:"false"
Also draft from the linked assessment’s documents.
Set to false to draft from the documents alone.
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.
string
required
custom_field, record_field, tag, contact, or location.
string
default:"vendor"
vendor, service, engagement, or assessment.
string
default:"fixed"
fixed writes by option rule or copies the answer. ai asks the model, and needs instruction.
string
The custom field, for a custom_field target.
string
The record column, for a record_field target.
string
What the model should write, for ai mode.
boolean
default:"true"
Whether a reviewer confirms the change before approval writes it.
boolean
default:"false"
Also answer the question from the record.
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

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.
string
accepted, rejected, or null to clear the verdict.
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

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.
string
The trigger question.
string
one_of, not_one_of, date_after, date_on_or_after, date_before, or date_on_or_before.
string[]
Option IDs, or one Unix timestamp at UTC midnight (as a string) for a date predicate.
object[]
Instead of the three fields above, up to 10 clauses (question_id, predicate, reference_values) that must all hold.
string[]
Questions to show.
string[]
Sections to show.
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

PATCH
PATCH /v1/questionnaire-conditions/{condition_id}
Takes the same fields as 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

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

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

POST
POST /v1/portals
Opens a request that asks one vendor to answer questionnaires, and returns 201 with the request. 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.
string
required
Vendor (cbvndr_...).
string[]
required
Questionnaires to ask for, in order.
string
default:"informational"
irq, informational, or vendor_onboarding. Any other type returns 422 with code invalid_type.
integer
Unix timestamp the vendor should answer by.
string
Message the vendor sees on the request.
string[]
Scope IRQ and informational answers to these services of the vendor.
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

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.
integer
Unix timestamp the vendor should answer by.
string
Message the vendor sees on the request.

Pause and resume a request

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.
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

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.
string
required
Subject. Template variables resolve.
string
required
Body. Template variables resolve.
string[]
required
Recipients.
string[]
Copy recipients.
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

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

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.
GET
GET /v1/portal-templates/{portal_template_id}
Returns one template, or 404 with code portal_template_not_found.
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.