Cognitive Services Pipeline API
The HTTP/JSON surface for the document-processing pipeline engine. Validate a pipeline, plan it against a document, and execute it against one or more images for OCR, document, and face extraction.
Base URL · Auth · ExamplesOverview
This REST facade exists so the frontend can drive the pipeline over plain JSON without a gRPC client. It is an interim REST facade in the Assentify Orchestrator (Assentify.ML.Face.SignalR) that forwards to the AI Interface. Endpoints and payload shapes are stable; the transport may later move to a dedicated Assentify.CognitiveServices service without changing this contract.
Three POST endpoints, all under /api/Pipeline. Pick by how much you want to happen:
| You want to… | Endpoint | Returns |
|---|---|---|
| Check pipeline YAML is well-formed | POST /api/Pipeline/Validate | JSON verdict (valid + issues) |
| Plan a pipeline against one image without running it | POST /api/Pipeline/DryRun | JSON plan (order, step plans, cost, status) |
| Run a pipeline against 1 to 10 images | POST /api/Pipeline/Execute | NDJSON stream, one line per image |
application/x-ndjson, one result object per line, in completion order.Base URL, environments, and auth
All calls target the Orchestrator base URL:
{ORCHESTRATOR_BASE_URL}/api/Pipeline/...
| Env | Host (confirm with platform team) |
|---|---|
| dev | https://dev.widgets.socket.assentify.com |
| qa / prod | same *.widgets.socket.assentify.com pattern, confirm exact hosts with DevOps |
Content type is application/json for all requests. Validate and DryRun return JSON; Execute streams application/x-ndjson. All field names are camelCase, both request and response.
CORS is already whitelisted for localhost:4200 (your Angular dev server) and the *.platform.assentify.com / *.sls.assentify.com origins.
document_type reference
Used by DryRun and Execute. Sent as an int.
| Value | Meaning |
|---|---|
| 0 | passport (default) |
| 1 | card |
| 4 | other_id |
| 5 | paper |
Flow toggles
Optional; available on DryRun and Execute (batch-level, applied to every image). All default off / AUTO.
| Field | Type | Meaning |
|---|---|---|
| documentAuth | bool | Run the document_auth step. |
| documentTampering | bool | Run the (non-blocking) tampering_analysis step. |
| classifyTemplate | bool | Force or skip bootstrap template classification. |
| disableDataExtraction | bool | Return raw OCR only (skip field extraction). |
| scanTemplate | bool | Skip authenticity and blurriness validations. |
| requiredFields | string[] | Restrict or target field extraction to these fields. |
| ocrType | int | OCR engine: 0 = AUTO, 1 = ZONAL, 2 = FULL_CARD (LLM), 3 = FREE_OCR. |
Base64 rules
- Send raw base64 or a
data:URI (data:image/jpeg;base64,…). The server strips thedata:…,prefix and any whitespace or newlines before processing. - The per-image size cap is measured on the cleaned (decoded) payload.
- Invalid base64 (bad length) makes that image's line
ok:false,errorType:"validation".
1 · Validate a pipeline
Validate pipeline YAML, optionally in the context of a template.
| Name | Type | Required | Description |
|---|---|---|---|
| pipelineYaml | string | Yes | The YAML to validate. |
| templateId | string | No | When set, pipelineYaml is merged onto the doc-type defaults this template resolves to, and the merged result is validated. Empty = validate the raw YAML only. |
valid: false is a normal 200 result, a validation verdict, not an HTTP error. Render errors and warnings in your next() handler. Only a missing pipelineYaml (400) or a backend transport fault (502) reaches the error path.Request
POST {BASE}/api/Pipeline/Validate
Content-Type: application/json
{
"pipelineYaml": "steps:\n - detect\n - ocr\n - validate",
"templateId": "8e0f...c1"
}
Response: 200 ValidatePipelineResponse
{
"valid": false,
"errors": [{ "code": "...", "message": "...", "path": "..." }],
"warnings": [{ "code": "...", "message": "...", "path": "..." }]
}
2 · Dry-run (plan) a pipeline
Resolve and plan a pipeline for one document without executing it. Returns execution order, per-step plans, estimated cost, the resolved YAML, and the single status the request would resolve to. The frontend supplies the template (referenceId) and the image.
| Name | Type | Required | Description |
|---|---|---|---|
| referenceId | string | Yes | The template to plan against (a.k.a. TemplateId). |
| imageFile | string (base64) | Yes | Image to plan against. Raw base64 or a data: URI (see Base64 rules). |
| documentType | int | No | See document_type. Default 0. |
| correlationId | string | No | Trace id; generated if omitted. |
| pipelineYaml | string | No | Inline YAML (bypasses PowerDoc template lookup). |
| flow toggles | - | No | See Flow toggles. |
Request
POST {BASE}/api/Pipeline/DryRun
Content-Type: application/json
{
"referenceId": "8e0f...c1",
"imageFile": "/9j/4AAQSk...",
"documentType": 1,
"documentTampering": true,
"ocrType": 1
}
Response: 200 DryRunPipelineResponse
{
"valid": true,
"executionOrder": ["detect", "ocr", "validate"],
"stepPlans": [
{ "id": "detect", "phase": "...", "conditional": false,
"abortStatus": "DetectIDFailed", "onFailure": "ABORT", "...": "..." }
],
"estimatedCost": { "...": "..." },
"resolvedPipelineYaml": "steps:\n ...",
"errors": [],
"status": 210
}
status is an Enums.Status number in the same space as Execute's response.status: valid:false maps to Error (-1); valid:true maps to the success status for the documentType. That lets you compare a planned outcome to a real one without translating names. Per-step failure statuses are names on stepPlans[].abortStatus; onFailure is the policy (ABORT | continue | skip | return_partial | fallback:<id>; a gate carries on_true / on_false).3 · Execute a pipeline (batch, streaming)
Execute a pipeline against one or more images. Images are processed with bounded concurrency and each result is streamed back as it completes, one NDJSON object per line, so the UI can render progress per image.
Request body: ExecutePipelineBatchDto
| Name | Type | Required | Description |
|---|---|---|---|
| images | array | Yes | 1 to 10 image items (see below). |
| referenceId | string | No | Default template for every image (image can override). |
| documentType | int | No | Default doc type for every image (image can override). |
| pipelineYaml | string | No | Inline YAML applied to every image. |
| params | object (string→string) | No | Runtime $params.* overrides for every image. |
| referenceIds | string[] | No | Restrict template classification to these ids. |
| flow toggles | - | No | See Flow toggles, applied to every image. |
Each image item (images[]):
| Name | Type | Required | Description |
|---|---|---|---|
| imageFile | string (base64) | Yes | The image. Raw base64 or data: URI. |
| correlationId | string | No | Echoed back on this image's result line so you can map it. Generated if omitted. |
| referenceId | string | No | Per-image template override. |
| documentType | int | No | Per-image doc-type override. |
Request
POST {BASE}/api/Pipeline/Execute
Content-Type: application/json
Accept: application/x-ndjson
{
"referenceId": "8e0f...c1",
"documentType": 1,
"documentTampering": true,
"params": { "locale": "ar" },
"images": [
{ "imageFile": "/9j/4AAQSk...", "correlationId": "front" },
{ "imageFile": "/9j/4AAQSk...", "correlationId": "back" }
]
}
Upload limits (this endpoint)
| Limit | Value | On exceed |
|---|---|---|
| Per image (decoded) | 5 MB | that image's line = ok:false, errorType:"validation", batch continues |
| Images per batch | 10 | whole request 400 before streaming |
| Total request body | 25 MB | 413 before the body is read |
Streaming response (application/x-ndjson)
HTTP 200, then one JSON object per line (\n-delimited). Lines arrive in completion order, not submission order. Use index or correlationId to attribute them.
// one object per line, flushed as each image finishes
{ "index":0, "correlationId":"front", "ok":true, "response":{ ...ExecutePipelineResponse... }}
{ "index":1, "correlationId":"back", "ok":false, "errorType":"transport", "error":"deadline exceeded" }
Each line (StreamedPipelineResult):
| Name | Type | Description |
|---|---|---|
| index | int | 0-based position in the submitted images array. |
| correlationId | string | Echoed (or generated), map the line back to your image. |
| ok | bool | true = pipeline call completed; false = failed. |
| response | object | null | The ExecutePipelineResponse when ok:true. |
| errorType | string | null | When ok:false: "validation" (bad input, do not retry), "transport" (backend gRPC error, retry may help), "error" (unexpected server fault). |
| error | string | null | Human-readable detail when ok:false. |
response (when ok:true): the full ExecutePipelineResponse
{
"success": true,
"status": 210,
"error": "",
"response": {
"scannedDoc": "...", "faces": ["..."], "croppedCard": "...", "croppedMrz": "...",
"classifiedTemplate": "...", "rawResult": "...",
"zonalResult": { "...": "..." },
"finalFields": { "...": "..." },
"fields": { "...": "..." },
"mrzFields": { "...": "..." },
"documentAuth": true, "isAllowed": true, "flag": false, "documentTampering": false,
"faceLive": true, "croppedFace": "...", "faceEncoding": "...",
"digitalSignature": "...", "decodedData": "...", "decodedFace": "...", "decodedSignature": "...",
"heatmap": "...", "tamperingPercentage": 0
}
}
line.response is the envelope; line.response.response holds the extracted document/face/signature fields. Reaching for line.response.fields directly is undefined, it is line.response.response.fields.Error cases (before streaming starts)
| Status | Format | Meaning |
|---|---|---|
| 400 | ProblemDetails (application/problem+json) | images empty or > 10. |
| 413 | - | total body over 25 MB. |
application/x-ndjson, parse the body as a single ProblemDetails JSON, do not try to line-split it.Consuming the Execute stream (Angular)
fetch with a ReadableStream reader for this one endpoint. Validate and DryRun stay on HttpClient.The wrapper below turns the NDJSON stream into an Observable<StreamedPipelineResult> that emits one line at a time and aborts the request on unsubscribe.
// pipeline-stream.ts
import { Observable } from "rxjs";
export function executePipeline$(
baseUrl: string,
body: ExecutePipelineBatchDto,
): Observable<StreamedPipelineResult> {
return new Observable<StreamedPipelineResult>((subscriber) => {
const controller = new AbortController();
(async () => {
const res = await fetch(`${baseUrl}/api/Pipeline/Execute`, {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/x-ndjson" },
body: JSON.stringify(body),
signal: controller.signal,
});
// Pre-stream errors (400 too many/empty, 413 too large) arrive as a single JSON body.
if (!res.ok || !res.headers.get("content-type")?.includes("application/x-ndjson")) {
const problem = await res.json().catch(() => ({}));
throw new Error(`Execute failed (${res.status}): ${problem.detail ?? problem.title ?? res.statusText}`);
}
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let nl: number;
while ((nl = buffer.indexOf("\n")) >= 0) { // emit each complete line
const line = buffer.slice(0, nl).trim();
buffer = buffer.slice(nl + 1);
if (line) subscriber.next(JSON.parse(line) as StreamedPipelineResult);
}
}
const tail = buffer.trim(); // flush any final partial line
if (tail) subscriber.next(JSON.parse(tail) as StreamedPipelineResult);
})()
.then(() => subscriber.complete())
.catch((err) => { if (!controller.signal.aborted) subscriber.error(err); });
return () => controller.abort(); // unsubscribe -> abort the request
});
}
fetch resolves outside Angular's zone, so emissions will not trigger change detection on their own. Either update signals (recommended, they notify regardless of zone), call ChangeDetectorRef.markForCheck(), or wrap the handler in NgZone.run(...). Using the async pipe with the Observable also works, since it schedules CD on each emission.Component usage, attribute each line by index / correlationId and branch on errorType:
import { Component, inject, signal } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { environment } from "./environment";
@Component({ /* ... */ })
export class BatchRunnerComponent {
private destroyRef = inject(DestroyRef);
readonly results = signal<Record<string, StreamedPipelineResult>>({});
run(files: string[]): void {
const body: ExecutePipelineBatchDto = {
referenceId: this.templateId,
documentType: 1,
documentTampering: true,
images: files.map((b64, i) => ({ imageFile: b64, correlationId: `img-${i}` })),
};
executePipeline$(environment.orchestratorBaseUrl, body)
.pipe(takeUntilDestroyed(this.destroyRef)) // leaving the view aborts the request
.subscribe({
next: (line) => {
this.results.update((r) => ({ ...r, [line.correlationId]: line }));
if (line.ok) {
this.applyResult(line.correlationId, line.response!.response); // note: .response.response
} else if (line.errorType === "validation") {
this.showBadInput(line.correlationId, line.error!); // do NOT retry
} else {
this.showRetryable(line.correlationId, line.error!); // transport/error -> retryable
}
},
error: (err) => this.toast(String(err.message ?? err)), // pre-stream 400/413
});
}
}
Retry and error handling
| Signal | Meaning | Action |
|---|---|---|
| errorType: "validation" | Bad input, missing / oversized / invalid image. | Do not retry. Fix the input. |
| errorType: "transport" | Backend gRPC error, the backend hiccuped. | Retryable. |
| errorType: "error" | Unexpected server fault. | Retryable. |
| Pre-stream 400 | images empty or > 10. | Fix the batch before resending. |
| Pre-stream 413 | Total body over 25 MB. | Split into smaller batches. |
correlationId and rebuild a minimal images[] from them.Quick reference
| Endpoint | Method | Body | Success | Notable errors |
|---|---|---|---|---|
| /api/Pipeline/Validate | POST | ValidatePipelineDto | 200 {valid, errors, warnings} | 400 no yaml · 502 backend |
| /api/Pipeline/DryRun | POST | DryRunPipelineDto | 200 DryRunPipelineResponse | 400 no referenceId/imageFile · 502 backend |
| /api/Pipeline/Execute | POST | ExecutePipelineBatchDto | 200 NDJSON stream | 400 empty/>10 · 413 >25 MB · per-line ok:false |