SteveSteve

Submission Review

Operate Steve as a headless review queue through the external API.

The submission endpoints let an integrator run manual review outside the Steve UI.

Use them to:

  • list submissions for the authenticated company
  • fetch full detail, including file download URLs and event history
  • inspect and resolve fraud matches
  • approve, reject, or cancel submissions

Public status model

Steve exposes the following submission statuses through the external API:

StatusMeaning
createdSubmission record exists but processing has not started
processingSteve is running extraction and fraud checks
capture_quality_blockedProcessing stopped because the uploaded capture did not meet quality rules
reviewHuman review is required
approvedApproved and waiting for or entering downstream sync
syncedApproved, and every downstream delivery has been settled (derived)
rejectedRejected by a reviewer or integration
cancelledCancelled before approval or sync
failedProcessing failed for a non-review reason

For workflow sessions, GET /api/v1/jobs/{sessionId} tells you when processing is done. GET /api/v1/submissions/{submissionId} is the canonical read model for manual review.

List submissions

curl "$BASE_URL/api/v1/submissions?status=review&workflow=receipt-ocr&limit=20" \
  -H "Authorization: Bearer $STEVE_API_KEY"

Supported query parameters:

ParameterMeaning
workflowFilter to one workflow slug
statusFilter by public submission status
fraudStatusFilter by clean, flagged, or blocked
cursorContinue from a previous page
limitPage size, clamped to 1..100

Example response:

{
  "data": [
    {
      "id": "jx7sub456submissions",
      "submissionStatus": "review",
      "fraudStatus": "flagged",
      "fraudScore": 72.5,
      "workflow": {
        "slug": "receipt-ocr",
        "name": "Receipt OCR",
        "version": 3
      },
      "extractedData": {
        "merchantName": "Biedronka",
        "totalAmount": 123.45
      },
      "confidence": 0.96,
      "createdAt": "2026-03-25T10:00:00.000Z",
      "clientSubmissionId": "order-1711353600000"
    }
  ],
  "pagination": {
    "hasMore": false,
    "nextCursor": null
  }
}

Notes:

  • timestamps are ISO 8601 strings
  • clientSubmissionId is the caller-supplied idempotency key from session creation
  • extractedData may be partial or complete depending on processing state

Get submission detail

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions" \
  -H "Authorization: Bearer $STEVE_API_KEY"

Example response:

{
  "id": "jx7sub456submissions",
  "submissionStatus": "review",
  "fraudStatus": "flagged",
  "fraudScore": 72.5,
  "extractedData": {
    "merchantName": "Biedronka",
    "totalAmount": 123.45,
    "currency": "PLN"
  },
  "confidence": 0.96,
  "fieldConfidences": {
    "merchantName": 0.99,
    "totalAmount": 0.95
  },
  "certainty": 0.94,
  "aiSummary": "Receipt from Biedronka with one flagged fraud match.",
  "failedReason": null,
  "rejectReason": null,
  "cancelReason": null,
  "workflow": {
    "id": "jx7wf001workflows",
    "slug": "receipt-ocr",
    "name": "Receipt OCR",
    "version": 3
  },
  "files": [
    {
      "id": "jx7file001submissionFiles",
      "fileIndex": 0,
      "label": "receipt",
      "mimeType": "image/jpeg",
      "fileSize": 245120,
      "downloadUrl": "https://r2.example.com/files/receipt.jpg?X-Amz-Signature=abc123"
    }
  ],
  "fraudMatches": [
    {
      "id": "jx7fm001submissionFraudMatches",
      "checkType": "visual",
      "score": 72.5,
      "similarity": 0.88,
      "status": "flag",
      "resolution": null,
      "resolvedBy": null,
      "resolvedAt": null
    }
  ],
  "events": [
    {
      "type": "ready_for_review",
      "occurredAt": "2026-03-25T10:01:40.000Z",
      "details": {
        "compliance": 0.96,
        "fraudStatus": "flagged"
      }
    }
  ],
  "clientSubmissionId": "order-1711353600000",
  "metadata": {
    "orderId": "12345"
  },
  "createdAt": "2026-03-25T10:00:00.000Z"
}

Important details:

  • file download URLs are pre-signed and valid for one hour
  • events are newest-first and capped at 100 entries
  • rejectReason and cancelReason are exposed on rejected or cancelled submissions

Fraud match workflow

If a submission is flagged or blocked, inspect the linked fraud matches:

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions/fraud-matches" \
  -H "Authorization: Bearer $STEVE_API_KEY"

Response shape:

{
  "data": [
    {
      "id": "jx7fm001submissionFraudMatches",
      "checkType": "visual",
      "score": 72.5,
      "similarity": 0.88,
      "status": "flag",
      "resolution": null,
      "resolvedAt": null,
      "targetSubmission": {
        "id": "jx7sub200submissions",
        "createdAt": "2026-03-25T09:43:20.000Z"
      }
    }
  ],
  "pagination": {
    "hasMore": false,
    "nextCursor": null
  }
}

Resolve one match:

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions/fraud-matches/jx7fm001submissionFraudMatches/resolve" \
  -X POST \
  -H "Authorization: Bearer $STEVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"resolution":"false_positive"}'

Allowed resolutions:

  • false_positive
  • confirmed_fraud

Approvals are blocked while unresolved fraud matches remain.

Reading checkType

checkType is either visual (perceptual-hash image duplicate) or data (duplicate extracted content). Data duplicates are produced two ways, and both report checkType: "data":

  • an exact match on the workflow's configured identity fields, which always arrives as score: 100, similarity: 1.0, status: "block"
  • a similarity match scored against the workflow's thresholds, which can be flag or block

Treat similarity: 1.0 with status: "block" as a certain duplicate rather than a high score.

Approve, reject, and cancel

Approve

Approves a submission that is currently in review.

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions/approve" \
  -X POST \
  -H "Authorization: Bearer $STEVE_API_KEY"

Response:

{
  "submissionId": "jx7sub456submissions",
  "workflow": {
    "slug": "receipt-ocr",
    "version": 3
  },
  "submissionStatus": "approved"
}

Approval triggers Steve's downstream sync path. Later reads may show synced, which appears once every delivery for the submission has settled; it is owned by the delivery ledger, not by review actions, and the status stays approved while any delivery is still outstanding. synced therefore requires an actual delivery to have gone out — or a workflow that delivers nowhere at all. A submission whose delivery was skipped for a fixable reason (no customer email on file, or the integration not configured yet) stays approved until someone fixes the cause and the delivery is retried.

Reject

Reject requires a human-readable reason.

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions/reject" \
  -X POST \
  -H "Authorization: Bearer $STEVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Document is illegible and cannot be verified."}'

Cancel

Cancel is available until the submission reaches approved or synced. The reason is optional.

curl "$BASE_URL/api/v1/submissions/jx7sub456submissions/cancel" \
  -X POST \
  -H "Authorization: Bearer $STEVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason":"Duplicate submission from the same customer."}'

Operational guidance

  • Use GET /api/v1/jobs/{sessionId} to know when a submission is ready for review.
  • Use GET /api/v1/submissions/{submissionId} as the canonical source of truth after any action.
  • If you need file bytes, fetch them immediately after retrieving submission detail because download URLs expire.
  • POST review actions share the write rate-limit bucket. See Error Handling.
  • Webhooks are useful for notification, but the safest way to render a review UI is still to refetch the submission after each event.

On this page