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 sendsis_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 returns409 with code version_locked. To change it, draft a new version in Coverbase, then edit the draft.
List questionnaires
GET
GET /v1/questionnairesstring
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.
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/questionnaires201 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}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}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}/clone201. 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}/contentid 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.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}/responseslimit (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}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}/autofill202 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.
boolean
default:"true"
Set to false to draft from the documents alone.
string[]
Draft from these uploaded files only.
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, sendPOST /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.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}/decision403 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, sendPOST /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, sendPOST /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}/conditionsclauses. 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.
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}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}/archive204. API keys get 403 with code api_key_not_allowed.
List the IRQ library
GET
GET /v1/irq-libraryslug, name, description, num_questions, and risk_category.
Open a request to a vendor
POST
POST /v1/portals201 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.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}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}/pause409 with code portal_not_open.
POST
POST /v1/portals/{portal_id}/resumedue_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}/invitations201 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.
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. SendPOST /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}/submissionsid, status, submitted_at, and submitted_by_email_address.
Review a submitted round
To send a submitted round back to the vendor, sendPOST /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-templatesportal_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}404 with code portal_template_not_found.
POST
POST /v1/portal-templates/{portal_template_id}/portals201 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, sendPOST /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}/submissionsstarts a draft round on an open persistent portal and returns201. 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-templatereturns the portal’s reminder template, ornull. Addinherit=trueto fall back to the template it inherits.GET /v1/portals/{portal_id}/invitation-email-templatereturns the email the portal’s invitation sends.GET /v1/portals/{portal_id}/invitation-recipientsreturns theto,cc, andbccan invitation would address. Passemail_template_idto apply that template’s default copy recipients. It sends nothing.POST /v1/portals/{portal_id}/apply-templaterebuilds one assessment portal fromportal_template_id, or from the org default when you sendnull. The template and the assessment plan keep their settings.POST /v1/portal-directory-exposure-checkstakesauth_configandask_configsand returnsexposes_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, sendPOST /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, sendPOST /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-sessionslists intake requests, newest first. Filter withstatuses,vendor_id,user_id,submitter_scope, andsearch(the vendor name).POST /v1/intake-sessions/querytakes a unified filter,search,sort,limit, andoffset.POST /v1/intake-sessions/exportstakes the same filter and returnsdownload_url,file_name, andexpires_in_secondsfor an Excel workbook. The link is short-lived, and the response is not cached.PATCH /v1/intake-sessions/{id}updates vendor details,session_data, orstatus. Settingstatustosubmittedsubmits the request with the same checks as the dashboard. Accepting or rejecting a request stays a human decision: API keys get403with codeapi_key_not_allowedforaccepted,rejected, orpartial.PUT /v1/intake-sessions/{id}/questionnaire-responsesaves one draft answer and merges it onto the stored one.POST /v1/intake-sessions/{id}/vendor-inforesearches the vendor invendor_search_input.POST /v1/intake-sessions/{id}/potential-vendorsstoresuse_caseand proposes vendors from the web.POST /v1/intake-sessions/{id}/vendor-suggestionssuggests qualified vendors. These run AI and web search and can take a minute.POST /v1/intake-sessions/{id}/follow-up-questionsgenerates the follow-up questions the request’s questionnaires need.POST /v1/intake-sessions/{id}/process-questionsstarts drafting every answer in the background. Poll the request untilis_processing_questionsis false.GET /v1/intake-sessions/{id}/prequalificationreturns the prequalification standing of the request’s vendor.GET /v1/intake-sessions/{id}/requester-journeyandGET /v1/workflows/runs/{workflow_run_id}/requester-journeyshow where a request stands and what blocks it.
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 return404 with code not_enabled.
GET /v1/intake-sessions/{id}/triagereturns the review card: duplicate and Do Not Use matches, IRQ scoping, the disposition and its sign-offs, and the reviewer’s questions.PUT .../triage/identifierssetstax_id,duns_number, andlei. The requester may set them too.PUT .../triage/dispositionrecords 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(withrole) andPOST .../triage/disposition/decline(withreason) are a sign-off group’s decision.PUT .../triage/blocked-override(withrationale) lets a request go ahead although its vendor is Do Not Use.PUT .../triage/budget-exceptiondecides a budget exception. API keys get403with codeapi_key_not_allowedon all four.POST .../triage/questionssends the request back to its requester withquestions. The requester is notified in Coverbase, or emailed when they have no account. The requester answers withPOST .../triage/questions/{question_id}/answer.POST .../triage/irqs/sendsends the IRQs the scoping requires, if they have not gone yet.PUT .../triage/procurement-originrecords the requisition the request came from.POST .../triage/material-change/irq-refreshopens an engagement’s IRQ refresh.GET .../triage/budget-lineslists the synced budget lines the spend can map to.GET .../triage/requester-noticereturns what the requester is told.GET /v1/intake-triage/configandPUT /v1/intake-triage/configread 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.