The admin application key
An admin application key is a tenant-wide key. In the Minerva dashboard it appears as the Admin (tenant-wide) key type.- Only an Owner or Admin can create one, and only in Live mode. See Create an admin key for the steps.
- The key acts only while the person who created it is still an owner or admin of the organization. If that person is demoted, disabled, or removed, every request made with the key is refused with
403. Create a replacement key from an account that still holds the role. - Workspace-scoped and tenant-scoped keys are refused with
403and the message “An admin application key is required.” - The key belongs to one organization. It is never accepted for another organization’s
orgId. - Changes made with the key are recorded in the list’s audit trail under the application’s name, not under a person.
Authentication and base URL
Send every request to the custom lists base URL, with your organization ID in place of{orgId}:
admin-api.gominerva.com is deprecated for API clients. It keeps working for
now, but new and updated integrations should call api.gominerva.com. The
paths change as well as the host:admin-api.gominerva.com/v2/organizations/{orgId}/custom-lists/…becomesapi.gominerva.com/custom-lists/v2/organizations/{orgId}/…admin-api.gominerva.com/v2/organizations/{orgId}/workspaces/…becomesapi.gominerva.com/workspaces/v2/organizations/{orgId}/…admin-api.gominerva.com/v2/webhooks/screening/…becomesapi.gominerva.com/webhooks/v2/screening/…
Authorization header. The legacy x-api-key header is also accepted.
orgId must be the organization the key was created in. Successful responses wrap the result in a data field.
Object model
Two rules shape every integration:
- Publishing moves every workspace where the list is already active onto the new version. You only need to activate a workspace once.
- A change reaches screening only when the deployment state is
live. A202from publish means the deployment has started, not that it has finished.
Endpoints
The Custom Lists section of the Admin Actions - Endpoints reference in this tab lists every request and response field. The tables below summarize each family. Paths are relative to/custom-lists/v2/organizations/{orgId}.
Lists
POST requires name. default_feed_category is one of internal, sanctions, pep, news, legal, or criminal and defaults to internal. record_id_column names the column that holds the record UID in uploaded files, defaults to record_uid, and can only be set when the list is created.
Records
{recordId}accepts the record’srecord_uid, so you can address records by your own identifier. URL-encode it.- A record needs
nameandentity_type(individual,organization, orunknown).POSTalso needsrecord_uid. Optional fields arealiases,countries,date_of_birth,date_of_incorporation,reason,notes, and acustom_fieldsmap of strings. PATCHreplaces the record’s content. Send the complete record: fields you leave out are cleared.POSTreturns409when therecord_uidalready exists on the list, including on an archived record.limitis 1 to 1000. The response’sdata.totalis the total number of matching records.
Publishing
publish and discard require expected_revision, the working_set_revision you read from pending changes. If the working set changed after you read it, the request returns 409 and nothing is published.
Activation and status
- The activation body takes
enabled(defaulttrue). When you switch a list on,feedCategoryandversionIdare required. - A list on the
internalfeed can be activated only in a workspace whose match scoring sets an Internal threshold for every workflow. Otherwise the request returns400and names the missing workflows. - If the workspace’s screening feed allowlist is set and leaves out the list’s feed, activation returns
400with, for example, “Internal must be enabled in the workspace before a custom list can be activated for that feed.” A workspace with an empty allowlist accepts every feed. - The status response carries
deployment_status(pending,live, ordown),workspace_activation_status(not_activated,activated, ordisabled),latest_version_id,active_version_id, anddeployment_stage. When the state isdown,deployment_failure_reasonexplains the failure when one is available.
Bulk uploads
Recipe: sync an offboarded-clients register from your CRM
This walkthrough keeps a list of offboarded clients in step with a CRM. Each run sends the clients added to or changed in the register since the last run, archives clients removed from it, publishes, makes sure the list is active in each workspace, and waits until screening uses the new version. Only then does it mark the CRM entries as synced. Set these variables first:1. Create the list once
Create the list on the first run and store its ID in your integration’s configuration. Later runs reuse it.cURL
201 with the list in data. Keep data.id as LIST_ID. To find an existing list instead, call GET $BASE and match on name.
2. Add or update each record
Use the CRM’s client ID asrecord_uid. Update the record first, addressing it by record_uid. PATCH replaces the whole record rather than merging into it, so send the complete record from the CRM every time: name, entity_type, and every other field you want to keep. A field you leave out is cleared, and a body without name or entity_type returns 400.
cURL
200means the record existed and now holds this content. Ifdata.statusisarchived, the client was removed earlier and has returned to the register. Restore it withPUT $BASE/$LIST_ID/records/CRM-10442/statusand{"status": "active"}.404with the JSON body{"message": "not found"}means the record is new. A404with any other body, such as “API-key authentication was denied.”, is an error, not a new record. Add a new record withPOST, sending the same body plus"record_uid": "CRM-10442":
cURL
3. Archive records removed from the register
cURL
4. Read the working-set revision
cURL
has_changes is false, nothing needs publishing; skip to step 6 to confirm the list is live.
5. Publish
Send the revision you just read asexpected_revision:
cURL
202 with the new version in data. Keep data.id as VERSION_ID. Publishing starts the deployment in every workspace where the list is already active.
If the response is 409, the working set changed after you read the revision, for example because another process or a dashboard user edited the list. Nothing was published. Read pending changes again and retry with the new revision.
6. Activate the list in each workspace, once
Check whether the list is already active in the workspace:cURL
data.workspace_activation_status is activated, the publish has already moved this workspace onto the new version; go to step 7. Otherwise, activate the version you published:
cURL
data.active_version_id from the status response as VERSION_ID.
If activation returns 400 because the workspace’s screening feed allowlist leaves out Internal, or because its match scoring has no Internal threshold, fix the workspace configuration first. Then make sure the workspace’s screening requests include the Internal feed, as described under Object model. Otherwise the list goes live but is never searched.
7. Wait until the deployment is live
Subscribe instead of poll
Acustom_list_deployment webhook tells your integration when a deployment goes live or down, so it does not have to poll. For workspace scope, create the webhook once in each workspace where you activate the list, with the same admin key. Alternatively, register one tenant-scoped destination to cover all current and future workspaces. Send x-workspace-id: a workspace-scoped webhook created without it lands in the organization’s default workspace, and it never fires for a list that is not active there.
cURL
/webhooks/v2/screening on api.gominerva.com. The response carries the webhook key; store it in your secrets manager and check it against the X-Webhook-Key header on every delivery. You can also create or edit the webhook on the Developers page in the dashboard; edits there keep the subscriptions you created through the API.
When a delivery arrives:
- Dedupe on the
X-Webhook-Delivery-IDheader. A retried delivery repeats the same ID. - When
meta.statusisliveandmeta.versionIdequalsVERSION_ID, screening in the workspace named bymeta.workspaceIduses the new version. Once every activated workspace reports this, go to step 8. - Treat
custom_list.deployment.downas a failure: either this deployment failed, or the list was live and stopped being available for screening. Leave the CRM entries unsynced and alert your team.meta.failure.messagedescribes the failure; contact support@gominerva.com if it persists. - Ignore a
pendingdelivery whosemeta.occurredAtis older than the last delivery you processed for that list and workspace. Deliveries can arrive out of order, and publishing a different list can briefly sendpendingand thenlivefor this list.
Poll as a fallback
Without a webhook, poll the status endpoint with backoff, for example every 10 seconds at first and doubling up to every 2 minutes:cURL
pending: the version is still deploying. Keep polling.livewithlatest_version_idequal toVERSION_ID: screening in that workspace uses the new version.down: the deployment failed.deployment_stagenames the step that failed, anddeployment_failure_reasonexplains it when available. Leave the CRM entries unsynced, alert your team, and contact support@gominerva.com if it persists.
8. Mark the CRM entries as synced
Mark the CRM entries from this run as synced only when every workspace reportslive on VERSION_ID and requests the Internal feed in its onboarding screening, monitoring, or API searches. If the run stops earlier, the next run sends the same changes again; updates by record_uid are safe to repeat.
Bulk alternative: upload a file
For a full export rather than a stream of changes, upload a CSV or XLSX file.full_replace archives every record that is missing from the file, which makes the file the complete source of truth; upsert_only keeps them.
-
Download the template with
GET $BASE/$LIST_ID/uploads/template(addformat=xlsxfor Excel) and match its column headers. Each row needs the record UID column,name, andentity_type. -
Upload the file:
The response returnscURL
202with the upload indata. Keepdata.idasUPLOAD_IDanddata.job_idasJOB_ID.record_id_columnmust match the list’srecord_id_column. -
Poll
GET $BASE/jobs/$JOB_IDuntildata.statusiscompletedorfailed, then readGET $BASE/$LIST_ID/uploads/$UPLOAD_ID. When itsstatusisready_for_review, checkadds,updates,unchanged,archives, andvalidation_errors. When it isfailed, fix the rows listed invalidation_errorsand upload again. -
Publish the upload:
The response returnscURL
202with a newdata.job_id. Poll the job until it iscompleted; the upload’sstatusis thenpublished. -
Read the new version ID from
GET $BASE/$LIST_ID/versions, which lists the newest version first, then continue with steps 6 to 8 of the recipe.
/publish with expected_revision. If the list changes between validation and publish, the upload publish returns 409. Upload the file again.
Errors
Error bodies come in two forms. Authentication errors and some400 validation errors are plain text. All other errors are JSON with a message field, and most custom-list errors also carry an error code. Parse the body as JSON only when Content-Type is application/json.
Retry
408, 429, and 503 with exponential backoff, for example starting at 2 seconds and doubling up to 2 minutes, with random jitter so parallel workers do not retry together. Retry a 409 only after reading the current state again.
Related documentation
- Custom Lists Guide for the dashboard view of lists, versions, and activation
- Workspace Management API for creating admin keys and configuring workspaces
- API Keys for the full key lifecycle
- Match Scoring Guide for the Internal thresholds that Internal activation needs