Workflows
Workflows are configurable processing pipelines. Define a sequence of steps such as capture, OCR, extraction, validation, checks, enrichment, risk, review, and output, then run them against uploaded documents or accumulated case data.
API credential mode
These resources currently require live API credentials. Test-mode API keys receive
403 on session, session-configuration and authenticated workflow routes,
including reads. Separate sandbox sessions and workflow runs are not supported.
Use an isolated development organization with live credentials for integration
testing; its results must not authorize production actions.
Creating a Workflow
import { KoralaClient } from '@korala/api-client';
const korala = new KoralaClient({
apiKeyId: 'your-api-key-id',
apiSecret: 'your-api-secret',
});
const workflow = await korala.workflows.create({
name: 'ID Document Extraction',
slug: 'id-document',
steps: [
{ type: 'capture', config: { acceptedTypes: ['application/pdf', 'image/png', 'image/jpeg'], maxSizeMb: 10 } },
{ type: 'ocr', config: { provider: 'auto' } },
{ type: 'extract', config: { prompt: 'inline', schema: 'inline', inlineSystemPrompt: '...', inlineOutputSchema: { ... } } },
{ type: 'validate', config: { schema: 'inline', aiValidation: { prompt: '...', fields: ['document_number'], failOnInvalid: false } } },
{ type: 'output', config: { format: 'json', includeMetadata: true } },
],
});Output schema
A workflow’s outputSchema is a JSON Schema object describing the structured result each run returns. The extract step asks the model for exactly these fields, and validate checks the result against them. In the dashboard it is edited under Output fields; through the API you set it on create or update:
await korala.workflows.update('invoice-check', {
outputSchema: {
type: 'object',
required: ['vendor_name', 'total'],
properties: {
vendor_name: { type: 'string', description: 'Who sent the invoice' },
total: { type: 'number' },
category: { type: 'string', enum: ['Food', 'Travel', 'Office supplies'] },
},
},
});- The steps read
type(string,number,integer,boolean,object,array),enum,items,properties,requiredanddescription. Other keywords (format,minLength,$ref,oneOf…) are stored and returned unchanged; the dashboard shows such fields as Advanced and keeps them as they are. nullremoves the schema. Without one, anextractstep with an inline prompt returns whatever fields the model finds in the document, with no fixed set, and avalidatestep withschema: 'inline'has nothing to check against and fails.- Property keys are the keys in the run’s result, and the names other steps refer to (for example
aiValidation.fields). Renaming a field through the API does not rename those references. propertiescome back in the order you sent them, which is the order the dashboard shows the fields in. The exception is a key made only of digits ("2024"): JavaScript lists those first, whatever order they were sent in, so the dashboard refuses such a name for a new or renamed field.
Step Types
| Step | Description |
|---|---|
capture | Accept document upload (PDF, images) |
ocr | Extract text from document via OCR |
extract | Structured data extraction with an LLM |
validate | Assertions over already-known data, extracted fields, or prior outputs |
enrich | Transforms or data augmentation that do not create first-class findings |
check | Provider-backed or specialized verification that emits findings, scores, and review signals |
risk | Aggregate scoring across evidence, history, and prior steps |
review | Human-in-the-loop pause point |
webhook | Fire-and-forget HTTP POST |
output | Format and return results |
Use check when the step calls an external or specialized verification source and you want to persist first-class findings such as PEP, sanctions, adverse media, or business registry results. Keep validate for deterministic assertions over data you already have.
HTTP check responses
A check step using the http provider sends a JSON POST to its
providerConfig.endpoint. A successful HTTP status alone does not clear the
check: the endpoint must return a complete JSON object with an explicit result.
The smallest completed response is:
{ "status": "clear" }Completed results accept status values clear, match, review_required, or
error. state may be omitted or set to completed. For example:
{
"state": "completed",
"status": "review_required",
"matches": [{ "label": "Possible name match", "category": "sanctions" }],
"reasons": ["An analyst must verify whether this is the same person."],
"reviewRecommended": true,
"providerRef": "screening-example-123"
}Optional completed fields include numeric score, matches (each with a string
label), string-array reasons, boolean reviewRecommended, nullable string
providerRef, and object metadata / rawResultRedacted. Scores use the
provider’s scale and must be within −2,147,483,648 to 2,147,483,647; they are rounded
for persistence. If omitted, reviewRecommended defaults to true for
review_required and error, and false for other statuses. The workflow’s
blocking policy still determines how the finding affects the session.
For asynchronous work, return a pending response instead of a terminal status:
{
"state": "pending",
"operationId": "screening-example-123",
"retryAfterMs": 5000
}operationId must be a nonblank string of at most 2,048 characters. Optional
retryAfterMs must be an integer from 1 through 86,400,000 milliseconds; it
defaults to 5,000 and intervals below 1,000 are raised to 1,000. Pending responses
may include object metadata, but cannot include status. When polling, Korala
POSTs to the same endpoint with the operation ID and metadata in pendingState.
These interval limits do not impose an overall operation lifetime.
Both response forms must be UTF-8 JSON, at most 256 KiB and 64 object/array levels
deep, received within the 10-second request deadline. Empty bodies, null, {},
invalid field types, non-success HTTP statuses, and unreadable, oversized or
timed-out responses produce an error finding with review recommended, rather
than a clear result. Strings and object keys must not contain NUL or unpaired
Unicode surrogates.
The workflow configuration owns kind, provider, blockingMode, and
reviewThreshold; response fields cannot override them. An HTTP check cannot
be saved with blockingMode: "block": only the definite results listed in
automatic decisions can decline a
session. Unknown response fields
are ignored. Keep private diagnostics out of evidence metadata and use
rawResultRedacted only for redacted data.
Existing integrations: endpoints that previously returned an empty successful
response must now explicitly return { "status": "clear" } when the check
clears. Move any blocking policy supplied in the response into the workflow
configuration.
Running a Workflow
// 1. Start a run and get upload URL
const run = await korala.workflows.startRun('id-document', {
inputData: { customField: 'value' },
});
// 2. Upload the document
await fetch(run.uploadUrl, {
method: 'PUT',
body: documentFile,
headers: { 'Content-Type': documentFile.type },
});
// 3. Confirm upload to start processing
await korala.workflows.confirmUpload(run.runId);
// 4. Poll for results
const result = await korala.workflows.getRun(run.runId);
// result.status: 'completed' | 'running' | 'failed'
// result.outputData: extracted dataA run starts once. If a confirm’s answer is lost, confirming again is safe:
a run that already started answers 409 Conflict, so treat a 409 as
success and read the run. Any other 4xx means it did not start.
Workflow Webhooks
Subscribe to these events for real-time notifications:
| Event | Description |
|---|---|
workflow_run_completed | Workflow run finished |
workflow_run_failed | Workflow run failed |
Human Review
If a workflow includes a review step, it pauses for human review:
// Approve and continue
await korala.workflows.submitReview(runId, {
action: 'approve',
editedData: { correctedField: 'new value' },
});
// Or reject
await korala.workflows.submitReview(runId, {
action: 'reject',
reason: 'Document is unreadable',
});