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