Architecture
Deployment topology, network, authentication, API standards, versioning, error model, sandbox, observability, rate limits.
Architecture overview
flowchart TB
subgraph CALLER ["Caller-controlled estate"]
D365["Microsoft Dynamics 365<br/>Customer Service"]
ORCH["Orchestrator<br/>(workflow + business rules)"]
APIM[["Azure API Management<br/>(APIM v2 gateway)"]]
D365 --> ORCH
ORCH --> APIM
end
APIM -->|"OAuth2 / API key, TLS 1.3"| FGW
subgraph FACTION ["Faction (EU-resident)"]
direction TB
FGW[["Faction API Gateway"]]
FGW --> SVCS
subgraph SVCS ["Stateless service layer"]
direction LR
S1["Intent Classifier"]
S2["Info Extractor"]
S3["Customer Matcher"]
S4["Product Matcher"]
end
SVCS --> ENGINE["Faction Engine"]
ENGINE --> STORE[("Encrypted storage<br/>EU region")]
SVCS -.-> OBS["Observability<br/>logs · metrics · traces"]
end
SVCS -->|"Response JSON"| APIM
APIM --> ORCH
ORCH --> D365
SANDBOX["Sandbox / Staging<br/>(separate tenant)"] -.->|"parallel"| FGWDeployment topology
Faction runs as a multi-region deployment. Tenants are pinned to a single region.
| Region | Description |
|---|---|
eu-west-1 | Ireland. Default for EU-resident deployments. |
eu-west-2 | London. Required for UK-only data residency. |
eu-central-1 | Frankfurt. Available on request. |
Cross-region replication is disabled by default. Disaster recovery uses in-region multi-AZ deployment plus encrypted snapshots in a paired region (still within the EU).
Network architecture
| Surface | Description |
|---|---|
| Public ingress | Faction API Gateway, TLS 1.3, certificate via public CA. Reachable by caller's APIM. |
| Private peering (optional) | Azure Private Link supported for tenants who want to remove public internet from the path. |
| Egress | Faction does not initiate inbound calls into the caller's network in the default deployment. Webhook callbacks (if opted in) call back to caller-supplied URLs over TLS. |
Authentication flow
Default flow is OAuth2 client credentials.
# Step 1: APIM exchanges credentials for a token
POST https://auth.eu.faction.ai/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=...
&client_secret=...
&scope=intent.classify+extract.quote+match.customer+match.product{
"access_token": "eyJhbGc...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "intent.classify extract.quote match.customer match.product"
}# Step 2: APIM forwards the token on each call
POST https://api.eu.faction.ai/v1/intent/classify
Authorization: Bearer eyJhbGc...
X-Correlation-Id: 8f3c-b21
Content-Type: application/jsonTokens are short-lived (1 hour default; configurable). API key auth is supported as a fallback or for sandbox.
API standards
| Standard | Faction adheres to |
|---|---|
| HTTP semantics | RFC 7231. Idempotency per RFC 7234. |
| JSON | RFC 8259. UTF-8. |
| Timestamps | RFC 3339. Always UTC. |
| Country codes | ISO 3166-1 alpha-2. |
| Currency codes | ISO 4217. |
| Phone numbers | E.164. |
| OpenAPI | v3.0.3. |
| Pagination | Cursor-based. next_cursor field on list endpoints. |
Versioning policy
Versions appear in the URL path: /v1/..., /v2/.... Within a major version, additive changes only.
| Event | Notice |
|---|---|
| New optional field added | No notice; clients should ignore unknown fields. |
| New endpoint added | No notice. |
| Field deprecated | 90 days before removal, signaled via response headers. |
| Endpoint deprecated | 90 days. Response includes Deprecation and Sunset headers. |
| Major version EOL | 12 months from new major release. |
Error model
{
"error_code": "EXTRACTION_PARTIAL_FAILURE",
"message": "Human-readable summary.",
"correlation_id": "8f3c-b21",
"request_id": "req_01J...",
"details": { }
}| HTTP | error_code examples | When |
|---|---|---|
| 400 | INVALID_PAYLOAD, MISSING_REQUIRED_FIELD | Bad request from caller. |
| 401 | INVALID_TOKEN, EXPIRED_TOKEN | Auth issue. |
| 403 | SCOPE_INSUFFICIENT, TENANT_DISABLED | Auth ok but action not allowed. |
| 404 | JOB_NOT_FOUND | Async job ID unknown. |
| 409 | IDEMPOTENCY_KEY_CONFLICT | Same key, different payload. |
| 422 | EXTRACTION_PARTIAL_FAILURE | Partial result returned in details. |
| 429 | RATE_LIMITED | Honor Retry-After. |
| 500 | INTERNAL_ERROR | Unexpected server error. Retry safe. |
| 502 | UPSTREAM_ERROR | Downstream model service unavailable. Retry safe. |
| 504 | TIMEOUT | Server timeout. Retry safe with same idempotency key. |
Health endpoints
GET /healthz
HTTP/1.1 200 OK
{ "status": "ok", "version": "1.4.2" }
GET /readyz
HTTP/1.1 200 OK
{ "status": "ready", "checks": { "model_serving": "ok", "storage": "ok" } }
GET /readyz
HTTP/1.1 503 Service Unavailable
{ "status": "not_ready", "checks": { "model_serving": "degraded" } }Sandbox environment
Sandbox is a separate tenant with its own credentials at https://api.eu.sandbox.faction.ai. Capabilities:
- Same API surface as production.
- Synthetic catalogue and customer data, or a customer-supplied subset.
- Reduced SLA (best-effort).
- Used for integration testing, UAT, training, pre-production rehearsals.
Observability
Per-request metrics are emitted and queryable via the dashboards API:
| Metric | Description |
|---|---|
request_count | Total requests, by module, status, tenant. |
latency_ms | p50, p95, p99 per module. |
confidence_distribution | Histogram of confidence scores per module. |
threshold_routing | Counts of auto_accept / confirm / review / manual per module. |
feedback_volume | Rep edits per module per day. |
Logs include correlation IDs, request IDs, tenant ID, module, decision summary, outcome.
Rate limits
| Scope | Default limit | Notes |
|---|---|---|
| Per-tenant per-module | 60 req/sec | Burst up to 120/sec for 30 s. |
| Per-tenant total | 240 req/sec | Across all modules. |
| Async job creation | 10/sec | Higher on request. |
Rate limits are configurable per tenant. Production volumes are validated and provisioned before go-live.