FactionDocs
Technical Reference

Solution Flow

Sync vs. async patterns, idempotency, retries, error propagation, concurrency.

Sequence (full pipeline)

sequenceDiagram
    autonumber
    participant Mail as Mailbox
    participant D365 as D365 CS
    participant Orch as Orchestrator
    participant APIM as Azure APIM
    participant FAC as Faction APIs
    participant Rep as Sales Rep
    participant K8 as K8 (ERP)

    Mail->>D365: Inbound email
    D365->>Orch: Case created event
    Orch->>APIM: POST /v1/intent/classify
    APIM->>FAC: forward (auth, correlation_id)
    FAC-->>APIM: 200 { intent, confidence, rationale }
    APIM-->>Orch: 200
    alt intent == quote
        par parallel calls
            Orch->>APIM: POST /v1/extract/quote
            APIM->>FAC: forward
            FAC-->>APIM: 200 { line_items, delivery, ... }
            APIM-->>Orch: 200
        and
            Orch->>APIM: POST /v1/match/customer
            APIM->>FAC: forward
            FAC-->>APIM: 200 { customer_id, ship_to, ... }
            APIM-->>Orch: 200
        end
        Orch->>APIM: POST /v1/match/product (uses extracted line_items)
        APIM->>FAC: forward
        FAC-->>APIM: 200 { matches, alternatives, unmatched }
        APIM-->>Orch: 200
        Orch->>D365: Update case with enriched fields
        D365->>Rep: Pre-populated draft quote
        Rep->>D365: Review, edit, confirm
        D365->>K8: Create quote (existing flow)
    else intent != quote
        Orch->>D365: Route case per non-quote rules
    end

Sync vs. async

Faction supports both. The orchestrator chooses per call based on payload.

ModuleDefault modeAsync trigger
Intent ClassifierSyncPayload over 10 MB
Quote Info ExtractorSyncPayload over 10 MB or scanned PDFs detected
Customer MatcherSyncNever (always sync)
Product MatcherSyncLine-item count over 200

Async polling flow

POST /v1/extract/quote
Prefer: respond-async

HTTP/1.1 202 Accepted
{ "job_id": "job_01J...", "status_url": "/v1/jobs/job_01J..." }

GET /v1/jobs/job_01J...
HTTP/1.1 200 OK
{ "status": "processing" }

GET /v1/jobs/job_01J...
HTTP/1.1 200 OK
{ "status": "complete", "result": { ... } }

Async webhook flow

POST /v1/extract/quote
Prefer: respond-async
Faction-Callback-Url: https://example.com/api/faction/callbacks

Faction calls back with the result when ready. Callback delivery is retried up to 5 times with exponential backoff.

Idempotency

Every call accepts an optional Idempotency-Key header. If a key is reused within 24 hours with the same payload, Faction returns the original response without re-processing.

POST /v1/match/product
Idempotency-Key: case-CRM-2026-04-29-00417-product-match-v1
X-Correlation-Id: 8f3c-b21

Reusing a key with a different payload returns 409 Conflict.

Retry semantics

FailureRecommended retry behavior
5xx responseRetry with exponential backoff, up to 3 attempts. Reuse Idempotency-Key.
429 rate-limitedHonor Retry-After header.
Timeout (no response)Retry with same Idempotency-Key.
4xx (other than 429)Do not retry. Surface to operations team.

Error propagation

Faction returns structured errors. The orchestrator can surface them to D365 as case notes or route them to a dead-letter queue.

{
  "error_code": "EXTRACTION_PARTIAL_FAILURE",
  "message": "2 of 3 attachments processed; 1 attachment was password-protected and skipped.",
  "correlation_id": "8f3c-b21",
  "details": {
    "skipped_attachments": [
      { "filename": "rfq_v2.pdf", "reason": "password_protected" }
    ]
  }
}

A partial-success response (HTTP 422 or 200 with degraded results) is preferred over a hard failure where possible. The orchestrator decides whether to proceed or escalate.

Concurrent calls

The four modules can be called concurrently for the same case_id. Faction maintains case-scoped context internally during the request window so concurrent calls share state where useful (e.g., extracted line items can inform product matching even if both are called in parallel). Context is not persisted beyond the request window.

Cancellation and timeouts

ScenarioBehavior
Client closes connection during sync callServer completes the work but result is discarded. Repeating the call with the same Idempotency-Key returns the cached result.
Async job marked stale (no poll for 60 min)Job remains queryable for 24 hours; result is purged after that.
Per-tenant request timeoutConfigurable. Default 30 s for sync calls; raise to 90 s for extraction over large attachments.

On this page