Files
0pt/contracts/openapi.yaml
T
admin 410339e697
Release chain (G17, ADR-W16-003) / Build, push, G17 (SBOM/scan/attest/verify/evidence) (push) Failing after 5s
CI (SIAX Cloud) / integration (push) Skipped
masterplan-lock / check (push) Successful in 7s
CI (SIAX Cloud) / security (push) Successful in 17s
Security Scan (SIAX Cloud) / security (push) Successful in 21s
CI (SIAX Cloud) / quality (push) Successful in 47s
Deploy to Coolify / deploy (push) Failing after 10m25s
feat(contract): add routing + normalization operationIds (ADR-027) (#118)
2026-09-26 11:19:43 +00:00

956 lines
31 KiB
YAML

openapi: 3.1.0
info:
title: 0pt Data OS API
version: 1.0.0
description: >
SIAX Cloud Data OS — order-, estimat- och capability-API (0pt).
Single Source of Truth för HTTP-kontraktet (SSOT). Routehandlers i
app/api/* realiserar dessa kontrakt; se x-siax-status för vad som är
live vs kontrakt-first (target).
x-siax:
appId: 0PT
capabilityPrefix: 0pt
tenantBinding: header X-Tenant-Id (live), session (target — se BACKLOG P0-2)
servers:
- url: https://0pt.app
description: Production (edge plane)
- url: https://api.0pt.app
description: Target API-host (Caddy, EJ verifierad live)
security:
- bearerAuth: []
paths:
/api/health:
get:
operationId: 0pt.health
summary: Liveness + real per-dependency state
x-siax-status: live
security: []
responses:
"200":
description: Process is alive. Body reports honest per-dependency status (ok/error/not_configured/not_integrated) — see HealthResponse.
content:
application/json:
schema:
$ref: "#/components/schemas/HealthResponse"
/api/ready:
get:
operationId: 0pt.ready
summary: Readiness — fails closed when a load-bearing dependency is down
x-siax-status: live
security: []
responses:
"200":
description: Ready to serve real traffic.
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyResponse"
"503":
description: Not ready — a load-bearing dependency is not configured/reachable.
content:
application/json:
schema:
$ref: "#/components/schemas/ReadyResponse"
/api/estimates:
post:
operationId: 0pt.estimate_order
summary: Prisestimat för en datajob-konfiguration
x-siax-status: live
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EstimateRequest"
responses:
"200":
description: Estimat (öre)
content:
application/json:
schema:
$ref: "#/components/schemas/EstimateResponse"
"400":
$ref: "#/components/responses/BadRequest"
/api/orders:
post:
operationId: 0pt.create_order
summary: Skapa draft-order (50 % deposition startar jobbet)
x-siax-status: live
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateOrderRequest"
responses:
"201":
description: Order skapad
content:
application/json:
schema:
$ref: "#/components/schemas/OrderSummary"
"400":
$ref: "#/components/responses/BadRequest"
get:
operationId: 0pt.list_orders
summary: Lista ordrar (org-scoping saknas — se BACKLOG P0-3)
x-siax-status: live
x-siax-gap: S3 — ingen auth/org-scoping
security: []
responses:
"200":
description: Orderlista
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/OrderSummary"
/api/orders/{id}:
get:
operationId: 0pt.get_order
summary: Hämta order
x-siax-status: live
security: []
parameters:
- $ref: "#/components/parameters/OrderId"
responses:
"200":
description: Order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"404":
$ref: "#/components/responses/NotFound"
patch:
operationId: 0pt.update_order
summary: Uppdatera order
x-siax-status: live
security: []
parameters:
- $ref: "#/components/parameters/OrderId"
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
"200":
description: Uppdaterad order
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
"404":
$ref: "#/components/responses/NotFound"
/api/0pt:
get:
operationId: 0pt.capability_index
summary: Capability-index
x-siax-status: live
responses:
"200":
description: Index med endpoints
content:
application/json:
schema:
type: object
additionalProperties: true
/api/0pt/sources/{sourceId}/query:
post:
operationId: 0pt.query_source
summary: Bunden källfråga (enrich-primitiv)
x-siax-status: live
x-siax-gap: S10 — connection_config ur body (SSRF-primitiv, BACKLOG P0-7)
parameters:
- name: sourceId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SourceQueryRequest"
responses:
"200":
description: Källdata med provenance per fält
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/EnrichedRecord"
"400":
$ref: "#/components/responses/BadRequest"
/api/0pt/documents/capture:
post:
operationId: 0pt.capture_document
summary: Ta emot dokument (PII) för extraktion
x-siax-status: live
x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2)
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
"201":
description: Dokument registrerat
"400":
$ref: "#/components/responses/BadRequest"
get:
operationId: 0pt.list_documents
summary: Lista dokument
x-siax-status: live
x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2)
responses:
"200":
description: Dokumentlista
/api/0pt/documents/{documentId}/extraction:
post:
operationId: 0pt.extract_document
summary: Extrahera dokument (OCR/AI — env-guardad, 501 utan konfig)
x-siax-status: live
parameters:
- name: documentId
in: path
required: true
schema:
type: string
responses:
"200":
description: Extraktion — aldrig fabricerade värden
"404":
$ref: "#/components/responses/NotFound"
"501":
$ref: "#/components/responses/NotConfigured"
/api/0pt/lakehouse/mappings:
post:
operationId: 0pt.map_dataset_to_iceberg
summary: Registrera dataset↔Iceberg-mappning
x-siax-status: live
x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2)
responses:
"201":
description: Mappning registrerad
"400":
$ref: "#/components/responses/BadRequest"
get:
operationId: 0pt.list_iceberg_mappings
summary: Lista mappningar
x-siax-status: live
x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2)
responses:
"200":
description: Mappningslista
/api/0pt/lakehouse/mappings/{mappingId}/writes:
post:
operationId: 0pt.request_iceberg_write
summary: Iceberg-write (L4) — tung variant delegeras till N0D-jobb
x-siax-status: live
x-siax-gap: S5 — CL0UD fail-open (BACKLOG P0-5)
parameters:
- name: mappingId
in: path
required: true
schema:
type: string
responses:
"202":
description: Skrivning begärd (job_id eller synkront snapshot)
"400":
$ref: "#/components/responses/BadRequest"
/api/0pt/sources/rate-policies:
post:
operationId: 0pt.create_rate_policy
summary: Registrera käll-rate-policy
x-siax-status: live
x-siax-gap: S2 — tenant_id från body (BACKLOG P0-2)
responses:
"201":
description: Rate-policy skapad
get:
operationId: 0pt.list_rate_policies
summary: Lista rate-policies
x-siax-status: live
x-siax-gap: S2 — tenant_id från query (BACKLOG P0-2)
responses:
"200":
description: Rate-policylista
/api/screen:
get:
operationId: 0pt.screen_entity
summary: Sanktions-/PEP-screening via OpenSanctions
x-siax-status: live
security: []
parameters:
- name: q
in: query
required: true
schema:
type: string
responses:
"200":
description: Screeningträffar
"400":
$ref: "#/components/responses/BadRequest"
/v1/0pt/enrichment/company-lists:
post:
operationId: 0pt.enrich_company_list
summary: >
(TARGET) Capability-formen av enrichment: lista → enrichade poster med
bridge_provenance. Kontrakt-first — implementeras av 0pt-motor/edge
via N0D-jobb (job_type 0pt.enrichment.company_list).
x-siax-status: target
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/EnrichCompanyListRequest"
responses:
"202":
description: Jobb begärt — poll via /v1/0pt/jobs/{id} (target)
"401":
$ref: "#/components/responses/Unauthorized"
/v1/0pt/resolve/entities:
post:
operationId: 0pt.resolve_entities
summary: (TARGET) Entity resolution/dedupe (rapidfuzz idag, spaCy planerat)
x-siax-status: target
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ResolveEntitiesRequest"
responses:
"200":
description: Kluster med konfidens
"401":
$ref: "#/components/responses/Unauthorized"
/v1/0pt/provenance/records/{entityType}/{entityKey}:
get:
operationId: 0pt.get_provenance
summary: (TARGET) Provenance per fält — bridge_provenance (källa, färskhet, konfidens, level)
x-siax-status: target
security: []
parameters:
- name: entityType
in: path
required: true
schema:
type: string
enum: [company, contact, vehicle, ubo]
- name: entityKey
in: path
required: true
schema:
type: string
responses:
"200":
description: Provenance per fält
content:
application/json:
schema:
$ref: "#/components/schemas/ProvenanceRecord"
"401":
$ref: "#/components/responses/Unauthorized"
/v1/0pt/parsing/route:
post:
operationId: 0pt.route_parsing
summary: >
(TARGET) Parser-routing/selektiv OCR-beslut (BTI-DOC-02/03/04/05).
Kontrakt-first — real kod i 0pt-motor/motor/parsing/routing.py
(route_parser m.fl.), INTEGRATION_TESTED men ej driftsatt bakom
denna route.
x-siax-status: target
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RouteParsingRequest"
responses:
"200":
description: Routingbeslut (parser/OCR-gren) + ev. nyckeltalskonflikter mellan parsers
content:
application/json:
schema:
$ref: "#/components/schemas/RouteParsingResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
/v1/0pt/normalize:
post:
operationId: 0pt.normalize_value
summary: >
(TARGET) Lokal-medveten normalisering — belopp/skala/datum (BTI-NLP-03)
och identifierare/checksummor (BTI-NLP-02). Kontrakt-first — real kod
i 0pt-motor/motor/normalization/{amounts,identifiers}.py,
INTEGRATION_TESTED men ej driftsatt bakom denna route.
x-siax-status: target
security: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NormalizeValueRequest"
responses:
"200":
description: Normaliserat värde — format_valid/review, aldrig en påhittad korrigering
content:
application/json:
schema:
$ref: "#/components/schemas/NormalizeValueResponse"
"400":
$ref: "#/components/responses/BadRequest"
"401":
$ref: "#/components/responses/Unauthorized"
# ---------------------------------------------------------------------------
# Internal service-to-service (Python-engine bridge) — P-002 internal OpenAPI.
# Declared 2026-09-09 after the cross-product parity gate proved these two
# routes + credential LIVE in services/api/workers/{resolve,quality_bridge}.py
# while the contract never shipped them (E_PATH_NOT_DECLARED +
# E_AUTH_SCHEME_UNDECLARED). The wire is truth; the contract now matches it.
# ---------------------------------------------------------------------------
/api/0pt/internal/er/candidates:
post:
operationId: 0pt.internal.erCandidates
summary: Candidate generation for the Python ER worker. Blocking-key buckets, similarity scoring, company/work review boundary. X-Internal-Token required (verifyInternalServiceToken).
x-siax-status: target
tags: [EntityResolution, Internal]
security:
- internalToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_id, project_id, records, blocking_fields, compare_fields]
properties:
tenant_id: { type: string, minLength: 1 }
project_id: { type: string, minLength: 1 }
records:
type: array
minItems: 2
maxItems: 5000
items:
type: object
required: [record_id, fields]
properties:
record_id: { type: string, minLength: 1 }
fields:
type: object
maxProperties: 200
additionalProperties:
type: [string, number, "null"]
blocking_fields:
type: array
minItems: 1
maxItems: 20
items: { type: string, minLength: 1 }
compare_fields:
type: array
minItems: 1
maxItems: 50
items:
type: object
required: [field]
properties:
field: { type: string, minLength: 1 }
weight: { type: number, minimum: 0, maximum: 100 }
auto_match_threshold: { type: number, minimum: 0, maximum: 1 }
review_threshold: { type: number, minimum: 0, maximum: 1 }
correlation_id: { type: string }
responses:
"200":
description: Candidate pairs (status auto_match/review/abstain) + review queue.
content:
application/json:
schema:
type: object
required: [pairs, review_queue, blocked_comparisons, total_possible_comparisons]
properties:
pairs:
type: array
items:
type: object
required: [candidate_id, record_a, record_b, score, status, decided_by, blocking_key]
properties:
candidate_id: { type: string }
record_a: { type: string }
record_b: { type: string }
score: { type: number }
status: { type: string, enum: [auto_match, review, abstain, force_match, force_non_match] }
decided_by: { type: string }
blocking_key: { type: string }
explanation:
type: array
items: { type: object }
"401":
$ref: "#/components/responses/Unauthorized"
/api/0pt/quality/bridge-run:
post:
operationId: 0pt.internal.qualityBridgeRun
summary: Advisory quality bridge — auto-provisions baseline rules and runs QualityService; the Python quality worker calls through here with X-Internal-Token. Fail-open by design.
x-siax-status: target
tags: [Quality, Internal]
security:
- internalToken: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_id, dataset_id]
properties:
tenant_id: { type: string, minLength: 1 }
dataset_id: { type: string, minLength: 1 }
dataset_version: { type: integer }
correlation_id: { type: string }
responses:
"200":
description: Run + any open issues (advisory).
"401":
$ref: "#/components/responses/Unauthorized"
# ---------------------------------------------------------------------------
# Quality — P-002 internal OpenAPI (0pt quality delegation to N0D)
# ---------------------------------------------------------------------------
/api/0pt/quality/runs:
post:
operationId: 0pt.quality.run
summary: Execute quality checks for a dataset; delegates to N0D via n0dRunDataQuality when any rule needs the GE backend.
x-siax-status: target
tags: [Quality]
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [tenant_id, dataset_id]
properties:
tenant_id: { type: string }
dataset_id: { type: string }
dataset_version: { type: integer }
responses:
"201":
description: Quality run accepted (status=delegated when offloaded to N0D).
"404":
description: No active rules for the dataset.
get:
operationId: 0pt.quality.list_runs
summary: List quality runs for a tenant.
x-siax-status: target
tags: [Quality]
parameters:
- { name: tenant_id, in: query, required: true, schema: { type: string } }
responses:
"200":
description: List of quality runs.
/api/0pt/quality/checks:
get:
operationId: 0pt.quality.checks
summary: List active quality checks/rules for a tenant+dataset.
x-siax-status: target
tags: [Quality]
parameters:
- { name: tenant_id, in: query, required: true, schema: { type: string } }
- { name: dataset_id, in: query, required: true, schema: { type: string } }
responses:
"200":
description: Active quality rules.
/api/0pt/quality/reports:
get:
operationId: 0pt.quality.reports
summary: Retrieve quality reports / Great Expectations results for a run.
x-siax-status: target
tags: [Quality]
parameters:
- { name: run_id, in: query, required: true, schema: { type: string } }
responses:
"200":
description: Quality report.
# ---------------------------------------------------------------------------
# N0D interop — 0pt consumption of N0D owner operations (P-001 owner-seam)
# ---------------------------------------------------------------------------
/api/0pt/n0d/results/{jobId}:
get:
operationId: 0pt.n0dGetJobResult
summary: Retrieve the result of an N0D-delegated quality job (n0dGetJobResult). Polls N0D after the quality run is delegated.
x-siax-status: target
tags: [Quality, N0D]
parameters:
- { name: jobId, in: path, required: true, schema: { type: string } }
responses:
"200":
description: N0D job result (outcome, metrics, ST0RE artifact refs).
"404":
description: Job not found or foreign-tenant probe.
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: Zitadel-utfärdad token; verifieras i middleware mot ZITADEL_ISSUER
internalToken:
type: apiKey
in: header
name: X-Internal-Token
description: Internal service-to-service credential (verifyInternalServiceToken). Endast för service-worker-bryggor; aldrig exponerat utåt.
parameters:
OrderId:
name: id
in: path
required: true
schema:
type: string
responses:
BadRequest:
description: Ogiltig input
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
Unauthorized:
description: Saknad/ogiltig Bearer-token
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
NotFound:
description: Resursen finns inte
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
NotConfigured:
description: Integration ej konfigurerad (501)
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
schemas:
ErrorEnvelope:
type: object
required: [error]
properties:
error:
type: object
required: [code]
properties:
code:
type: string
description: error.code-envelope (t.ex. bad_json, storage_not_configured)
message:
type: string
HealthResponse:
type: object
description: >
db/engine/stripe report a real dependency probe (BACKLOG P0-3): "ok"
means a live round trip succeeded, "error" means it was configured
but unreachable (see `detail`), "not_configured" means the env var
is unset. `queue` can only ever be "not_configured" or
"not_integrated" -- no Redis client is wired in this repo, so
presence of REDIS_URL is reported honestly rather than faking a
connectivity check. `ok` is true only when no configured dependency
is in "error". This is a LIVENESS check: HTTP status stays 200
whenever the process can respond, regardless of dependency state --
see /api/ready for the readiness gate that fails closed.
required: [ok, service, db, queue, engine, stripe, ts]
properties:
ok:
type: boolean
service:
type: string
db:
type: string
enum: [ok, error, not_configured]
queue:
type: string
enum: [not_configured, not_integrated]
engine:
type: string
enum: [ok, error, not_configured]
stripe:
type: string
enum: [ok, error, not_configured]
detail:
type: object
description: Present only when `ok` is false -- one-line error message per failing dependency.
additionalProperties:
type: string
ts:
type: string
format: date-time
ReadyResponse:
type: object
description: >
Readiness (BACKLOG P0-3: "no /ready route existed"). Fails closed --
503 whenever a load-bearing dependency (today: only `db`) is not
both configured and reachable. Point a load balancer / Coolify
readiness probe here, not at /api/health.
required: [ready, service, db, queue, engine, stripe, ts]
properties:
ready:
type: boolean
service:
type: string
db:
type: string
enum: [ok, error, not_configured]
queue:
type: string
enum: [not_configured, not_integrated]
engine:
type: string
enum: [ok, error, not_configured]
stripe:
type: string
enum: [ok, error, not_configured]
ts:
type: string
format: date-time
EstimateRequest:
type: object
properties:
flow:
type: string
config:
type: object
additionalProperties: true
EstimateResponse:
type: object
properties:
estimate:
type: object
additionalProperties: true
CreateOrderRequest:
type: object
properties:
flow:
type: string
default: custom
config:
type: object
additionalProperties: true
OrderSummary:
type: object
required: [order_id, status]
properties:
order_id:
type: string
status:
type: string
deposit_ore:
type: integer
description: Deposition i öre
total_ore:
type: integer
description: Totalsumma i öre
Order:
type: object
properties:
order_id:
type: string
status:
type: string
config:
type: object
additionalProperties: true
SourceQueryRequest:
type: object
required: [tenant_id, source_id, query]
properties:
tenant_id:
type: string
description: (GAP) ska komma från session — BACKLOG P0-2
project_id:
type: string
source_id:
type: string
query:
type: string
connection_config:
type: object
additionalProperties: true
description: (GAP) SSRF-primitiv — BACKLOG P0-7
EnrichedRecord:
type: object
properties:
fields:
type: object
additionalProperties: true
provenance:
type: object
additionalProperties:
$ref: "#/components/schemas/FieldProvenance"
FieldProvenance:
type: object
required: [source_id, freshness, confidence, level]
properties:
source_id:
type: string
freshness:
type: string
format: date-time
confidence:
type: number
minimum: 0
maximum: 1
level:
type: integer
minimum: 0
maximum: 3
description: 0=ej tillgänglig · 1=licensierad · 2=authorized web · 3=officiell API/bulk
EnrichCompanyListRequest:
type: object
required: [market, entities]
properties:
market:
type: string
entities:
type: array
items:
type: object
additionalProperties: true
fields:
type: array
items:
type: string
output_artifact_type:
type: string
default: st0re.artifact
ResolveEntitiesRequest:
type: object
required: [records]
properties:
records:
type: array
items:
type: object
additionalProperties: true
threshold:
type: number
default: 0.85
algorithm:
type: string
enum: [rapidfuzz, target_spacy]
default: rapidfuzz
ProvenanceRecord:
type: object
additionalProperties:
$ref: "#/components/schemas/FieldProvenance"
RouteParsingRequest:
type: object
required: [digital_text_page]
properties:
digital_text_page:
type: boolean
description: True om sidan har ett digitalt textlager (ej scannad).
ocr_available:
type: boolean
default: false
parser_result:
type: object
description: Föregående parser-försök, om något (tom text = kandidat för OCR-gren).
properties:
parser:
type: string
text:
type: string
key_figures:
type: object
additionalProperties:
type: number
compare_with:
type: object
description: (Valfritt) ett andra parser-resultat att stämma av mot — avvikande nyckeltal blir en konflikt, aldrig en tyst ersättning.
properties:
parser:
type: string
text:
type: string
key_figures:
type: object
additionalProperties:
type: number
RouteParsingResponse:
type: object
required: [routing]
properties:
routing:
type: string
enum: [text_parser, ocr_branch, explicit_unavailable]
conflicts:
type: object
description: Fält där de två parser-resultatens nyckeltal skiljer sig (present endast om compare_with skickades).
additionalProperties: true
NormalizeValueRequest:
type: object
required: [kind, value]
properties:
kind:
type: string
enum: [amount, percent, date, identifier]
description: amount/percent/date = motor/normalization/amounts.py. identifier = motor/normalization/identifiers.py.
value:
type: string
description: Rå textsträng att normalisera (belopp, procentsats, datum eller identifierare).
locale:
type: string
description: (kind=amount, valfritt) comma_decimal|dot_decimal — auto-detekteras annars.
unit:
type: string
description: (kind=amount|percent) t.ex. SEK/KSEK/MSEK/GSEK eller "%"/"pp".
identifier_kind:
type: string
enum: [orgnr, cvr, vat, ocr]
description: (kind=identifier, krävs) vilken identifierartyp som ska valideras.
country:
type: string
description: (kind=identifier, krävs) ISO-3166 alpha-2, t.ex. SE/NO/DK.
description: >
Ett fält, en normalisering. amount/percent/date har alltid en explicit
precision/skala (aldrig en påhittad dag eller sammanblandad enhet).
identifier ger format_valid eller review — aldrig en tyst korrigerad
checksumma; existens i register verifieras inte här.
NormalizeValueResponse:
type: object
required: [kind, status]
properties:
kind:
type: string
enum: [amount, percent, date, identifier]
status:
type: string
enum: [ok, format_valid, format_invalid, review, unparseable]
result:
type: object
description: Normaliserat resultat, formen beror på kind (se motor/normalization/{amounts,identifiers}.py).
additionalProperties: true