Skip to content

Collect control evidence automatically

Most control evidence already exists somewhere: a nightly backup report, a quarterly access review export, a CI job’s dependency scan. The work is not producing it, it is remembering to file it — which is why it gets done in the week before an audit, from memory.

This guide files it as a side effect of the job that produces it.

You need: a key with controls:read and evidence:write. How to get one.

Evidence attaches to a control, so you need its id. Look it up once and put it in the job’s configuration — do not search on every run.

Terminal window
curl -sS "https://api.vendorica.com/v1/controls?search=backup&limit=5" \
-H "Authorization: Bearer $VENDORICA_API_KEY"

GET /v1/controls also filters by frameworkCode, domain, status and ownerUserId, which is the faster route when you are wiring up several controls at once.

One request carries the bytes and the control they belong to:

Terminal window
curl -X POST \
"https://api.vendorica.com/v1/controls/$CONTROL_ID/evidence?filename=backup-restore-test-2026-08-30.pdf&summary=Quarterly%20restore%20test&effectiveDate=2026-08-30" \
-H "Authorization: Bearer $VENDORICA_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @restore-test.pdf

The metadata rides in the query string; the body is the file. multipart/form-data works too if your HTTP client prefers it — see Uploading documents for both modes and the checksum behaviour.

Parameter
filename What a human will see in the evidence list. Put the date in it.
summary One line: what this artefact demonstrates.
description Longer context — how it was produced, what to check.
effectiveDate The date the evidence is about, not the upload date.
isSensitive Restricts the content to named people. See below.
  1. Produce the artefact as the job already does.

  2. Upload it in the same run, so the two cannot drift apart. A separate “upload yesterday’s reports” job is a second thing that can fail silently.

  3. Fail the job if the upload fails. Evidence that was not filed is evidence you do not have, and you want to find out on the day, not in the audit.

# GitHub Actions
- name: Quarterly restore test
run: ./scripts/restore-test.sh --out restore-test.pdf
- name: File the evidence
env:
VENDORICA_API_KEY: ${{ secrets.VENDORICA_API_KEY }}
run: |
curl -fsS -X POST \
"https://api.vendorica.com/v1/controls/${{ vars.BACKUP_CONTROL_ID }}/evidence\
?filename=restore-test-$(date +%F).pdf\
&summary=Automated%20quarterly%20restore%20test\
&effectiveDate=$(date +%F)" \
-H "Authorization: Bearer $VENDORICA_API_KEY" \
-H "Content-Type: application/pdf" \
--data-binary @restore-test.pdf

curl -f is doing real work there: without it curl exits 0 on a 403 and your pipeline goes green having filed nothing.

Set isSensitive=true and the artefact is restricted to its uploader, administrators and auditors — named people.

A key is nobody in particular, so a key cannot read it back. Listings return the row with its metadata and refuse the content. If your pipeline needs to verify what it filed, do not flag it sensitive; if the artefact genuinely contains material that should not be broadly readable, flag it and accept that verification is a human step.

Terminal window
curl -sS "https://api.vendorica.com/v1/evidence?controlId=$CONTROL_ID&limit=50" \
-H "Authorization: Bearer $VENDORICA_API_KEY"

GET /v1/evidence is cursor-paginated — it reads cursor / hasMore from a top-level cursor object, not pagination. The loop is the same one as the audit-log export.

Filters worth knowing: collectedFrom / collectedTo for a period, and includeSuperseded when you want the history rather than only what is current.

A useful monitor is the inverse of this call: for each control your pipelines cover, alert when the newest effectiveDate is older than the control’s expected cadence. That catches the failure this whole guide exists to prevent — a job that stopped filing months ago and told nobody.