Skip to main content
The Risk Assessment Agents API lets approved applications create and manage beta agent-driven risk assessments programmatically.
Beta API: Risk assessment agent endpoints require beta enablement and application access. Contact your Minerva representative or support@gominerva.com before building against these endpoints.

Base URL

Use the central Minerva API domain:
https://api.gominerva.com/risk-assessments/v2
Your Minerva contact will confirm when this public base URL is enabled for your application and environment. Internally, these endpoints route to Minerva’s risk assessment service customer API.

Authentication

Use a Minerva application API key.
Authorization: Api-Key YOUR_API_KEY
The legacy X-Api-Key header is also accepted by the service, but new integrations should use the Authorization: Api-Key ... header. The application key determines the tenant, workspace, and actor context. Do not send tenant or workspace identifiers in the request body unless Minerva has explicitly documented a field for that endpoint. For method-level endpoint reference, use Agent Risk Assessments - Endpoints in the API Reference sidebar.

API Usage Guide

The most common integration flow is:
  1. fetch available workflows
  2. create a draft assessment
  3. start the agent run
  4. poll the assessment summary until it is ready for review, blocked, or terminal
  5. add user input or steering when the agent needs more context
  6. read tasks and risks
  7. conclude the assessment after reviewer validation
Minerva responses use the standard envelope:
{
  "status": 200,
  "msg": "OK",
  "result": {
    "assessment": {},
    "summary": {},
    "tasks": []
  }
}
Read response data from result.assessment, result.summary, result.tasks, and similar result fields.

Create An Assessment

The exact workflow, scorecard, and subject fields available to your application depend on your beta configuration. Fetch workflows and scorecards first, then create an assessment using the selected IDs.
curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Northstar Holdings KYB onboarding review",
    "workflow_id": "workflow_kyb_onboarding",
    "context": {
      "purpose": "KYB onboarding",
      "description": "Review registration, ownership, screening exposure, and adverse media before onboarding."
    },
    "subjects": [
      {
        "type": "organization",
        "name": "Northstar Holdings Inc.",
        "registration_id": "123456789",
        "jurisdiction": {
          "country": "CA",
          "state": "ON"
        }
      }
    ]
  }'
The response includes the assessment_id. Save it for subsequent lifecycle, upload, run, review, and report steps.

Create And Start In JavaScript

const API_BASE = "https://api.gominerva.com/risk-assessments/v2";

async function minerva(path, { method = "GET", body } = {}) {
  const response = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: `Api-Key ${process.env.MINERVA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: body ? JSON.stringify(body) : undefined,
  });

  const payload = await response.json();
  if (!response.ok) {
    throw new Error(payload?.msg || `Minerva API error ${response.status}`);
  }
  return payload.result;
}

const { assessment } = await minerva("/assessments", {
  method: "POST",
  body: {
    title: "Northstar Holdings KYB onboarding review",
    purpose: "KYB onboarding",
    workflow_id: "workflow_kyb_onboarding",
    context: {
      description:
        "Review registration, ownership, screening exposure, and adverse media before onboarding.",
    },
    subjects: [
      {
        type: "organization",
        name: "Northstar Holdings Inc.",
        locations: [{ type: "headquarters", country: "CA", state: "ON" }],
        registrations: [
          {
            registration_id: "123456789",
            registry_name: "Ontario Business Registry",
            jurisdiction: { level: "state", country: "CA", state: "ON" },
          },
        ],
      },
    ],
  },
});

await minerva(`/assessments/${assessment.assessment_id}/start`, {
  method: "POST",
  body: {
    reason: "Start automated KYB review from onboarding case CASE-12345.",
  },
});

Start The Agent Run

curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/start \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "reason": "Start automated EDD investigation from case CASE-12345."
  }'
Starting an assessment creates an agent run owned by the risk assessment service. Use the run and trajectory endpoints to inspect progress.

Poll Status And Completion

Use GET /assessments/{assessmentId}/summary for polling. It returns compact state, task status counts, client risk rating counts, timestamps, and the latest run reference.
curl -sS \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/summary \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  | jq '.result.summary | {
      assessment_id,
      status,
      task_status_counts,
      latest_run,
      updated_at,
      ready_at,
      concluded_at
    }'
Interpret assessment status as:
StatusMeaningIntegration behavior
draftAssessment exists but has not started.Start it when required input is present.
runningThe agent is actively working.Keep polling summary or trajectory.
waiting_for_userThe agent needs human/application input.Read trajectory, questions, confirmations, or commands, then call /input.
interruptedWork was paused or interrupted.Inspect trajectory and call /resume when ready.
ready_for_reviewAgent work is complete and ready for human/API review.Read tasks, risks, evidence, and CRR before concluding.
reopenedA previously reviewed or terminal assessment needs more work.Review comments/tasks and resume or add input as needed.
concludedFinal reviewer/API conclusion has been recorded.Treat as final for downstream systems.
cancelledAssessment was cancelled.Stop polling and record cancellation.
failedRun or assessment failed.Stop polling, capture context, and contact Minerva if repeatable.
ready_for_review means the agent work is complete. It is not the same as a final compliance decision. Use concluded only after your reviewer or integration has validated tasks, risks, evidence, and comments.

Polling Example

const REVIEW_READY = new Set(["ready_for_review"]);
const TERMINAL = new Set(["concluded", "cancelled", "failed"]);
const ATTENTION_REQUIRED = new Set(["waiting_for_user", "interrupted"]);

async function waitForAssessmentReview(assessmentId, options = {}) {
  const timeoutMs = options.timeoutMs ?? 30 * 60 * 1000;
  const intervalMs = options.intervalMs ?? 10_000;
  const startedAt = Date.now();

  while (Date.now() - startedAt < timeoutMs) {
    const { summary } = await minerva(`/assessments/${assessmentId}/summary`);

    if (REVIEW_READY.has(summary.status) || TERMINAL.has(summary.status)) {
      return summary;
    }

    if (ATTENTION_REQUIRED.has(summary.status)) {
      return summary;
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  throw new Error(`Timed out waiting for assessment ${assessmentId}`);
}
Use a polling interval appropriate to your workflow. For most integrations, 10 to 30 seconds is enough. Avoid tight polling loops.

Track Run Progress

Use the trajectory endpoint when you need a detailed activity stream, tool calls, intermediate messages, or evidence of why the agent is waiting.
curl -sS \
  "https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/trajectory?after_sequence=0&limit=100&sort_direction=asc" \
  -H "Authorization: Api-Key YOUR_API_KEY"
For incremental polling, store the highest returned sequence and pass it as after_sequence on the next call.
let afterSequence = 0;

async function readNewTrajectoryEvents(assessmentId) {
  const params = new URLSearchParams({
    after_sequence: String(afterSequence),
    limit: "100",
    sort_direction: "asc",
  });

  const result = await minerva(
    `/assessments/${assessmentId}/trajectory?${params}`,
  );

  const events = result.trajectory || result.events || [];
  for (const event of events) {
    afterSequence = Math.max(afterSequence, event.sequence || 0);
  }

  return events;
}

Check Task Completion

Use GET /assessments/{assessmentId}/tasks to inspect task status, notes, questions, confirmations, and client risk rating state.
curl -sS \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/tasks \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  | jq '.result | {
      task_status_counts,
      tasks: [
        .tasks[] | {
          task_id,
          title,
          required,
          status,
          tags,
          evidence_ids,
          risk_ids,
          completed_at,
          incomplete_at,
          requires_review_at
        }
      ],
      questions,
      confirmations,
      client_risk_ratings
    }'
Task statuses are:
StatusMeaning
pendingTask has not started.
in_progressTask is being worked.
completeTask is complete according to workflow requirements.
incompleteTask could not be completed with available evidence.
requires_reviewTask has output or risk that needs human/API validation.
Use task_status_counts for queue and completion checks, but read individual tasks before concluding.
function taskCompletionSummary(tasks) {
  const required = tasks.filter((task) => task.required);
  return {
    required_count: required.length,
    complete: required.filter((task) => task.status === "complete").length,
    requires_review: required.filter(
      (task) => task.status === "requires_review",
    ).length,
    incomplete: required.filter((task) => task.status === "incomplete").length,
    open: required.filter((task) =>
      ["pending", "in_progress"].includes(task.status),
    ).length,
  };
}

const { tasks } = await minerva(`/assessments/${assessmentId}/tasks`);
const completion = taskCompletionSummary(tasks);
To find work needing review across many assessments, use the list endpoint filters:
curl -sS \
  "https://api.gominerva.com/risk-assessments/v2/assessments?status=ready_for_review,reopened&task_status=requires_review,incomplete&sort_by=updated_at&sort_direction=desc&limit=25" \
  -H "Authorization: Api-Key YOUR_API_KEY"

Get Risks

Use GET /assessments/{assessmentId} to read the full assessment document. Risk findings are returned on result.assessment.risks.
curl -sS \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  | jq '.result.assessment.risks // []'
Each risk finding can include:
FieldUse
risk_idStable risk finding identifier.
titleShort risk title.
summaryHuman-readable finding summary.
severitylow, medium, high, or critical.
categoryBroad category such as screening, ownership, adverse media, or CRR.
tagsWorkflow-specific topic labels for more precise filtering.
statusactive, resolved, false_positive, or accepted.
subject_idsSubjects implicated by the finding.
task_idsWorkflow tasks associated with the finding.
evidence_idsEvidence supporting the finding.
source_urlsSource URLs supporting the finding, when available.
There is not currently a separate customer endpoint that lists risks independently from the assessment document. Filter risks client-side from the assessment payload.

Filter Risks By Topic, Tag, Status, Or Severity

Risk tags are configured by the workflow and generated during the assessment. Do not assume every tenant uses the same tag vocabulary. Use your workflow’s task tags and returned risk tags as the source of truth. Common topic groups might include:
Topic groupExample categories or tags
Screeningscreening, sanctions, pep, watchlist, criminal
Adverse media and legaladverse_media, legal, litigation, fraud, corruption, enforcement
Ownership and controlownership, control, ubo, nominee, corporate_structure
Geography and jurisdictiongeography, jurisdiction, high_risk_jurisdiction
Source of funds or wealthsource_of_funds, source_of_wealth, sof, sow
Client risk ratingcrr, client_risk_rating, risk_rating

Filter With jq

TOPIC="ownership"

curl -sS \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  | jq --arg topic "$TOPIC" '
      .result.assessment.risks // []
      | map(select((.status // "active") == "active"))
      | map(select(
          (.category == $topic)
          or ((.tags // []) | index($topic))
        ))
    '
To filter by several related tags:
curl -sS \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  | jq '
      ["sanctions", "pep", "watchlist"] as $topics
      | .result.assessment.risks // []
      | map(select((.status // "active") == "active"))
      | map(select(
          (.category as $category | $topics | index($category))
          or ((.tags // []) as $tags | any($topics[]; $tags | index(.)))
        ))
    '

Filter In JavaScript

const severityRank = {
  low: 1,
  medium: 2,
  high: 3,
  critical: 4,
};

function riskMatchesTopic(risk, topics) {
  const normalizedTopics = new Set(topics.map((topic) => topic.toLowerCase()));
  const category = String(risk.category || "").toLowerCase();
  const tags = (risk.tags || []).map((tag) => String(tag).toLowerCase());

  return (
    normalizedTopics.has(category) ||
    tags.some((tag) => normalizedTopics.has(tag))
  );
}

function filterRisks(
  risks,
  {
    topics = [],
    statuses = ["active"],
    minSeverity = "low",
    subjectIds = [],
    taskIds = [],
  } = {},
) {
  const allowedStatuses = new Set(statuses);
  const subjectFilter = new Set(subjectIds);
  const taskFilter = new Set(taskIds);
  const minRank = severityRank[minSeverity] || severityRank.low;

  return (risks || []).filter((risk) => {
    const status = risk.status || "active";
    if (!allowedStatuses.has(status)) return false;
    if ((severityRank[risk.severity] || 0) < minRank) return false;
    if (topics.length > 0 && !riskMatchesTopic(risk, topics)) return false;
    if (
      subjectFilter.size > 0 &&
      !(risk.subject_ids || []).some((id) => subjectFilter.has(id))
    ) {
      return false;
    }
    if (
      taskFilter.size > 0 &&
      !(risk.task_ids || []).some((id) => taskFilter.has(id))
    ) {
      return false;
    }
    return true;
  });
}

const { assessment } = await minerva(`/assessments/${assessmentId}`);

const sanctionsOrPepRisks = filterRisks(assessment.risks, {
  topics: ["sanctions", "pep", "watchlist"],
  minSeverity: "medium",
});

const ownershipRisks = filterRisks(assessment.risks, {
  topics: ["ownership", "control", "ubo", "nominee"],
  statuses: ["active", "accepted"],
});

Filter Risks By Task Tags

Task tags and risk tags should align with your workflow taxonomy. When you want “risks related to tasks tagged ownership,” read tasks and risks together:
const [{ assessment }, { tasks }] = await Promise.all([
  minerva(`/assessments/${assessmentId}`),
  minerva(`/assessments/${assessmentId}/tasks`),
]);

const ownershipTaskIds = new Set(
  tasks
    .filter((task) => (task.tags || []).includes("ownership"))
    .map((task) => task.task_id),
);

const ownershipTaskRiskIds = new Set(
  tasks
    .filter((task) => ownershipTaskIds.has(task.task_id))
    .flatMap((task) => task.risk_ids || []),
);

const risksForOwnershipTasks = (assessment.risks || []).filter((risk) => {
  return (
    ownershipTaskRiskIds.has(risk.risk_id) ||
    (risk.task_ids || []).some((taskId) => ownershipTaskIds.has(taskId))
  );
});

Provide Steering

Use steering when the run should adjust direction without creating a new assessment.
curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/steer \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Prioritize official registry evidence before adverse media. Add any parent entities discovered during ownership review as related subjects."
  }'
Good steering is specific, short, and tied to the current assessment. Do not use steering to bypass required review or evidence requirements. Use /steer when you want to change how the agent proceeds. Use /input when you are answering a question, providing missing facts, or adding more instructions as user-provided context.
curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/input \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Additional instruction from the onboarding case: treat the newly uploaded ownership chart as the customer-supplied version, but corroborate beneficial owners against official registry or reliable public sources before marking the ownership task complete.",
    "reason": "Customer supplied a revised ownership chart after the run started."
  }'
Both /steer and /input accept:
FieldUse
promptPlain-text instruction or answer.
messageAlternate plain-text instruction field. prompt is preferred.
reasonShort audit reason for the command.
payloadStructured JSON context for integration-specific instructions.
artifact_refsReferences to artifacts already known to the runtime, when needed.
run_idOptional explicit run id when using the latest-run endpoint.

Update Task Status

curl -sS -X PATCH \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/tasks/TASK_ID/status \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "requires_review",
    "comment_markdown": "Ownership task needs analyst review because the registry extract and ownership chart disagree."
  }'
Use status changes to reflect actual review state. Include comments when the reason will matter for audit or handoff.

Add A Comment

curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/comments \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_kind": "assessment",
    "subject_id": "ASSESSMENT_ID",
    "markdown": "Please confirm whether the uploaded ownership chart is the current signed version before concluding."
  }'
Comments should name the review question, the evidence or task involved, and the action needed.

Conclude An Assessment

curl -sS -X POST \
  https://api.gominerva.com/risk-assessments/v2/assessments/ASSESSMENT_ID/conclude \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "decision": "approved_with_conditions",
    "summary": "KYB review is complete. Ownership is understood, sanctions/PEP screening did not identify confirmed matches, and the remaining medium-risk ownership factor is documented in the CRR scorecard."
  }'
Conclude only after required tasks, evidence, risk findings, comments, and client risk rating criteria have been reviewed according to your policy.

Operational Guidance

  • Create separate application keys for test and Live use.
  • Confirm beta enablement before using the public route.
  • Fetch workflows and scorecards instead of hard-coding IDs where possible.
  • Store assessment IDs in your internal case system.
  • Use descriptive assessment titles so dashboard users can identify API-created cases.
  • Upload or register source documents before starting the run when they are material to the task.
  • Review trajectory and evidence IDs before concluding assessments.
  • Retry idempotent reads safely, but avoid repeated start or conclude calls without checking current status.
  • Contact Minerva if the same endpoint returns repeated failures for one tenant, workspace, or workflow.