Developers · Cognitive Services API Developer
API REFERENCE

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 · Examples
Base URL {ORCHESTRATOR_BASE_URL}/api/Pipeline Content type application/json JSON casing camelCase Execute application/x-ndjson stream

Overview

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…EndpointReturns
Check pipeline YAML is well-formedPOST /api/Pipeline/ValidateJSON verdict (valid + issues)
Plan a pipeline against one image without running itPOST /api/Pipeline/DryRunJSON plan (order, step plans, cost, status)
Run a pipeline against 1 to 10 imagesPOST /api/Pipeline/ExecuteNDJSON stream, one line per image
JSON vs streamValidate and DryRun return JSON. Execute streams 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/...
EnvHost (confirm with platform team)
devhttps://dev.widgets.socket.assentify.com
qa / prodsame *.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.

No app-level auth on these endpointsThese endpoints do not enforce app-level authentication and read no tenant or telemetry headers. They are reached through the platform gateway. Confirm with the platform team whether a gateway token is required in your target environment before shipping.

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.

ValueMeaning
0passport (default)
1card
4other_id
5paper
GotchaThere is no 2 or 3. The enum is exactly 0, 1, 4, 5. Build your dropdown from these four values, not a 0..n range.

Flow toggles

Optional; available on DryRun and Execute (batch-level, applied to every image). All default off / AUTO.

FieldTypeMeaning
documentAuthboolRun the document_auth step.
documentTamperingboolRun the (non-blocking) tampering_analysis step.
classifyTemplateboolForce or skip bootstrap template classification.
disableDataExtractionboolReturn raw OCR only (skip field extraction).
scanTemplateboolSkip authenticity and blurriness validations.
requiredFieldsstring[]Restrict or target field extraction to these fields.
ocrTypeintOCR engine: 0 = AUTO, 1 = ZONAL, 2 = FULL_CARD (LLM), 3 = FREE_OCR.

Base64 rules

  1. Send raw base64 or a data: URI (data:image/jpeg;base64,…). The server strips the data:…, prefix and any whitespace or newlines before processing.
  2. The per-image size cap is measured on the cleaned (decoded) payload.
  3. Invalid base64 (bad length) makes that image's line ok:false, errorType:"validation".

1 · Validate a pipeline

POST{BASE}/api/Pipeline/Validate
Body application/json 200 ValidatePipelineResponse 400 no yaml 502 backend

Validate pipeline YAML, optionally in the context of a template.

NameTypeRequiredDescription
pipelineYamlstringYesThe YAML to validate.
templateIdstringNoWhen 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.
Key behaviourvalid: 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

POST{BASE}/api/Pipeline/DryRun
Body application/json 200 DryRunPipelineResponse 400 no referenceId/imageFile 502 backend

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.

NameTypeRequiredDescription
referenceIdstringYesThe template to plan against (a.k.a. TemplateId).
imageFilestring (base64)YesImage to plan against. Raw base64 or a data: URI (see Base64 rules).
documentTypeintNoSee document_type. Default 0.
correlationIdstringNoTrace id; generated if omitted.
pipelineYamlstringNoInline YAML (bypasses PowerDoc template lookup).
flow toggles-NoSee 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
}
Reading the status and step plansstatus 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)

POST{BASE}/api/Pipeline/Execute
Body application/json Accept application/x-ndjson 200 NDJSON stream 400 empty/>10 413 >25 MB

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

NameTypeRequiredDescription
imagesarrayYes1 to 10 image items (see below).
referenceIdstringNoDefault template for every image (image can override).
documentTypeintNoDefault doc type for every image (image can override).
pipelineYamlstringNoInline YAML applied to every image.
paramsobject (string→string)NoRuntime $params.* overrides for every image.
referenceIdsstring[]NoRestrict template classification to these ids.
flow toggles-NoSee Flow toggles, applied to every image.

Each image item (images[]):

NameTypeRequiredDescription
imageFilestring (base64)YesThe image. Raw base64 or data: URI.
correlationIdstringNoEchoed back on this image's result line so you can map it. Generated if omitted.
referenceIdstringNoPer-image template override.
documentTypeintNoPer-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)

LimitValueOn exceed
Per image (decoded)5 MBthat image's line = ok:false, errorType:"validation", batch continues
Images per batch10whole request 400 before streaming
Total request body25 MB413 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):

NameTypeDescription
indexint0-based position in the submitted images array.
correlationIdstringEchoed (or generated), map the line back to your image.
okbooltrue = pipeline call completed; false = failed.
responseobject | nullThe ExecutePipelineResponse when ok:true.
errorTypestring | nullWhen ok:false: "validation" (bad input, do not retry), "transport" (backend gRPC error, retry may help), "error" (unexpected server fault).
errorstring | nullHuman-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
  }
}
Watch the double nestingline.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)

StatusFormatMeaning
400ProblemDetails (application/problem+json)images empty or > 10.
413-total body over 25 MB.
Detect pre-stream errors firstCheck the response status and Content-Type before you start reading NDJSON. If it is not 200 + application/x-ndjson, parse the body as a single ProblemDetails JSON, do not try to line-split it.

Consuming the Execute stream (Angular)

Do NOT use HttpClient for ExecuteExecute is a POST with a body, so EventSource (SSE) cannot be used. And Angular's HttpClient buffers the whole response by default, so you would lose the per-image progress that is the entire point of the stream. Use 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
  });
}
Change detectionfetch 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

SignalMeaningAction
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 400images empty or > 10.Fix the batch before resending.
Pre-stream 413Total body over 25 MB.Split into smaller batches.
Retry only the failed imagesWhen retrying, resend a batch containing just the failed images, never re-run the whole batch blindly. Track failures by correlationId and rebuild a minimal images[] from them.

Quick reference

EndpointMethodBodySuccessNotable errors
/api/Pipeline/ValidatePOSTValidatePipelineDto200 {valid, errors, warnings}400 no yaml · 502 backend
/api/Pipeline/DryRunPOSTDryRunPipelineDto200 DryRunPipelineResponse400 no referenceId/imageFile · 502 backend
/api/Pipeline/ExecutePOSTExecutePipelineBatchDto200 NDJSON stream400 empty/>10 · 413 >25 MB · per-line ok:false