openapi: 3.1.0
info:
  title: Zyntris ComplianceOS API
  version: 1.0.0
  summary: Tenant-isolated onboarding, screening, configurable monitoring and case workflow.
  description: >-
    Production requests use HTTPS at the ComplianceOS origin. Company onboarding
    is public and rate-limited. Other endpoints require a live bearer API key
    issued after manual company approval; approved organisations start a 15-day
    trial. Screening currently queries the synchronized UN Security Council
    Consolidated List only. Local sanctions and PEP sources and identity providers
    are not configured. Tenant-configured amount and velocity rules can generate
    triage alerts. Cases, investigation notes, draft-only reports, and audit data
    are available through scoped API endpoints. No regulator filing or legal
    decision is automated.
servers:
  - url: https://compliance.zyntris.org
security:
  - bearerAuth: []
tags:
  - name: Onboarding
  - name: Organisation
  - name: Customers
  - name: Screening
  - name: Transactions
  - name: Due Diligence
  - name: Verification
  - name: Risk
  - name: Monitoring
  - name: Alerts
  - name: Cases
  - name: Reports
  - name: Audit
paths:
  /v1/onboarding/applications:
    post:
      tags: [Onboarding]
      security: []
      summary: Submit a company profile for manual review
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [legalName, contactEmail, organisationType, jurisdiction, registrationNumber, privacyConsent]
              properties:
                legalName: { type: string, maxLength: 120 }
                contactEmail: { type: string, format: email, maxLength: 160 }
                organisationType: { type: string, maxLength: 80 }
                jurisdiction: { type: string, enum: [NG, GB, US, CA, ZM, RW] }
                registrationNumber: { type: string, maxLength: 80 }
                website: { type: string, format: uri, description: HTTPS only }
                intendedUse: { type: string, maxLength: 500 }
                privacyConsent: { type: boolean, const: true }
      responses:
        '201': { description: Application received for manual review. }
        '200': { description: Duplicate application returned without creating a second record. }
        '429': { description: Source rate limit reached. }
        '503': { description: Onboarding service is not configured. }
  /v1/organisations:
    get:
      tags: [Organisation]
      summary: Read the API key's organisation, enabled jurisdictions and trial period
      responses:
        '200': { description: Tenant profile including status and trial timestamps when applicable. }
        '401': { description: Missing, invalid, revoked or inactive key. }
  /v1/customers:
    post:
      tags: [Customers]
      summary: Create an organisation-scoped individual or entity reference
      description: Store only data required for the integration. Tenant-specific externalReference is idempotent.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, subjectType, jurisdiction]
              properties:
                name: { type: string, maxLength: 160 }
                subjectType: { type: string, enum: [individual, entity] }
                jurisdiction: { type: string, pattern: '^[A-Z]{2}$' }
                externalReference: { type: string, maxLength: 120 }
      responses:
        '201': { description: Customer reference created. }
        '200': { description: Existing tenant-scoped external reference returned. }
        '401': { description: API key required. }
        '403': { description: Scope or jurisdiction not enabled. }
  /v1/verifications:
    get:
      tags: [Verification]
      summary: List this tenant's verification sessions
      description: Requires verification:read. Results are isolated to the API key's organisation.
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
        - name: cursor
          in: query
          schema: { type: string, description: Opaque pagination cursor returned by the previous response. }
      responses:
        '200': { description: Tenant verification sessions and pagination cursor. }
        '403': { description: verification:read scope is required. }
    post:
      tags: [Verification]
      summary: Start an individual or business verification workflow
      description: Requires verification:write. Submitted facts and ownership are recorded as declared only. This operation does not run identity, registry, document, PEP, adverse-media or local-list provider checks, and never marks a subject verified.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string, maxLength: 120 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerId, workflowType, jurisdiction]
              properties:
                customerId: { type: string, description: Existing tenant-scoped customer ID. }
                workflowType: { type: string, enum: [individual, business] }
                jurisdiction: { type: string, pattern: '^[A-Z]{2}$' }
                facts:
                  type: object
                  description: Allowed declarative facts only (legal_name, trading_name, registration_number, registration_country, incorporation_date, business_type, industry, registered_address, operating_address, website, email, phone, full_legal_name, date_of_birth, nationality, residence_country, occupation, employer, source_of_funds, source_of_wealth, purpose, expected_activity). Government identity numbers and document files are not accepted.
                  additionalProperties: { type: string, maxLength: 500 }
                ownership:
                  type: array
                  maxItems: 50
                  items:
                    type: object
                    required: [name, partyType, relationshipType]
                    properties:
                      name: { type: string, maxLength: 160 }
                      partyType: { type: string, enum: [individual, business, unknown] }
                      relationshipType: { type: string, enum: [director, shareholder, beneficial_owner, controller, authorized_representative, intermediate_company] }
                      ownershipPercentage: { type: number, minimum: 0, maximum: 100 }
                      votingPercentage: { type: number, minimum: 0, maximum: 100 }
      responses:
        '201': { description: Session created; status is in_progress and outcome is unverified. }
        '200': { description: Previously created idempotent session returned. }
        '403': { description: Scope or jurisdiction not enabled. }
        '422': { description: Invalid customer, workflow type, facts or ownership declaration. }
  /v1/verifications/{verificationId}:
    get:
      tags: [Verification]
      summary: Read a tenant-owned verification and its declared evidence
      description: Requires verification:read. Facts include provenance and declared/verified state; declarations remain unverified absent an authorized source.
      parameters:
        - name: verificationId
          in: path
          required: true
          schema: { type: string }
      responses:
        '200': { description: Verification details, provenance, and ownership declarations. }
        '404': { description: Not found within the authenticated tenant. }
        '403': { description: verification:read scope is required. }
  /v1/screenings:
    post:
      tags: [Screening]
      summary: Screen an individual or entity against the configured official sanctions sources
      description: Searches the synchronized UN Security Council Consolidated List, including listed aliases, and returns ranked potential matches. A no-match result applies only to configured list sources and is not legal clearance. Potential matches and no-match results both require risk-based human review. National/regional lists, PEP, ownership/control and adverse media are not included.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, subjectType, jurisdiction]
              properties:
                name: { type: string, maxLength: 160 }
                subjectType: { type: string, enum: [individual, entity] }
                jurisdiction: { type: string, pattern: '^[A-Z]{2}$' }
                dateOfBirth: { type: string, format: date }
                registrationNumber: { type: string, maxLength: 80 }
      responses:
        '200': { description: UN list screening performed; result includes source timestamp, coverage limits, ranked candidates and reviewRequired=true. }
        '503': { description: Feed is missing, stale, or screening could not be completed; no clear result is returned. }
        '401': { description: API key required. }
  /v1/transactions/ingest:
    post:
      tags: [Transactions]
      summary: Screen named transaction parties and accept an idempotent transaction event
      description: Supply parties[] to screen each individual/entity against the synchronized UN list. Potential matches and configured rule triggers require human review; a no-match is not clearance.
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string, maxLength: 120 }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [customerReference, amount, currency, jurisdiction, occurredAt]
              properties:
                eventId: { type: string, maxLength: 120 }
                customerReference: { type: string, maxLength: 120 }
                amount: { type: string, pattern: '^-?\\d{1,15}(\\.\\d{1,4})?$' }
                currency: { type: string, pattern: '^[A-Za-z]{3}$' }
                jurisdiction: { type: string, pattern: '^[A-Z]{2}$' }
                occurredAt: { type: string, format: date-time }
                parties:
                  type: array
                  maxItems: 20
                  description: Named originator, beneficiary, counterparty or other parties to screen. Every item needs a name and subjectType.
                  items:
                    type: object
                    required: [name, subjectType]
                    properties:
                      name: { type: string, maxLength: 160 }
                      subjectType: { type: string, enum: [individual, entity] }
      responses:
        '202': { description: Stored; reports per-party screening and configured monitoring rule results. Human review remains required. }
        '200': { description: Idempotent duplicate returned. }
        '401': { description: API key required. }
        '403': { description: Scope or jurisdiction not enabled. }
  /v1/customers/{customerId}/due-diligence:
    parameters:
      - { name: customerId, in: path, required: true, schema: { type: string } }
    get:
      tags: [Due Diligence]
      summary: Read a tenant-owned due-diligence checklist
      responses:
        '200': { description: Current CDD status and checklist. }
        '404': { description: Customer or CDD record not found in this tenant. }
    put:
      tags: [Due Diligence]
      summary: Update CDD state and evidence checklist
      description: Audited update. Store status/reference data only; avoid excessive personal data.
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [status], properties: { status: { type: string, enum: [not_started, in_progress, awaiting_evidence, review, approved, rejected] }, rationale: { type: string, maxLength: 1000 }, checklist: { type: object } } }
      responses:
        '200': { description: Updated and audit logged. }
  /v1/customers/{customerId}/verifications:
    post:
      tags: [Verification]
      summary: Record consent and request a verification check
      description: Provider is not connected; response remains provider_required. Never send BVN/NIN values.
      parameters:
        - { name: customerId, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [checkType, consent], properties: { checkType: { type: string, enum: [identity_document, address, company_registry, beneficial_owner, bvn, nin, pep, sanctions_local] }, consent: { type: boolean, const: true } } }
      responses:
        '202': { description: Consent recorded; provider_required, not verified. }
  /v1/risk-assessments:
    get:
      tags: [Risk]
      summary: Read assessment history for a tenant-owned customer
      parameters:
        - { name: customerId, in: query, required: true, schema: { type: string } }
      responses:
        '200': { description: Tenant-owned risk assessment history and factors. }
    post:
      tags: [Risk]
      summary: Create an explainable provisional customer risk assessment
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [customerId], properties: { customerId: { type: string } } }
      responses:
        '201': { description: Triage factors returned; no decision is automated. }
  /v1/monitoring/rules:
    get:
      tags: [Monitoring]
      summary: List tenant transaction monitoring rules
      responses:
        '200': { description: Tenant-owned rule definitions. }
    post:
      tags: [Monitoring]
      summary: Create a tenant amount or velocity rule
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [name, ruleType, config], properties: { name: { type: string }, ruleType: { type: string, enum: [amount_threshold, velocity_count] }, severity: { type: string, enum: [low, medium, high, critical] }, enabled: { type: boolean, default: false }, config: { type: object, description: "amount_threshold: threshold + currency; velocity_count: count + windowMinutes" } } }
      responses:
        '201': { description: Rule created and audit logged. }
  /v1/alerts:
    get:
      tags: [Alerts]
      summary: List tenant monitoring and screening alerts
      responses:
        '200': { description: Tenant-owned alert queue. Requires alerts:read. }
  /v1/alerts/{alertId}:
    patch:
      tags: [Alerts]
      summary: Triage a tenant-owned alert with rationale
      parameters:
        - { name: alertId, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [status, rationale], properties: { status: { type: string, enum: [triaged, escalated, closed_false_positive, closed_no_action] }, rationale: { type: string, minLength: 8 } } }
      responses:
        '200': { description: Alert status updated and audit logged. }
  /v1/cases:
    get:
      tags: [Cases]
      summary: List tenant investigation cases
      responses:
        '200': { description: Tenant-owned cases. }
    post:
      tags: [Cases]
      summary: Open an investigation case
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [summary, jurisdiction], properties: { summary: { type: string }, jurisdiction: { type: string, enum: [NG, GB, US, CA, ZM, RW] }, caseType: { type: string }, customerId: { type: string } } }
      responses:
        '201': { description: Case opened and audit logged. }
  /v1/cases/{caseId}:
    patch:
      tags: [Cases]
      summary: Update investigation status with rationale
      parameters:
        - { name: caseId, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [status, rationale], properties: { status: { type: string, enum: [open, investigating, awaiting_sar_decision, closed] }, rationale: { type: string, minLength: 8 } } }
      responses:
        '200': { description: Case updated and audit logged. }
  /v1/cases/{caseId}/notes:
    get:
      tags: [Cases]
      summary: Read tenant-scoped investigation notes
      parameters:
        - { name: caseId, in: path, required: true, schema: { type: string } }
      responses:
        '200': { description: Case timeline notes. }
    post:
      tags: [Cases]
      summary: Add a tenant-scoped investigation note
      parameters:
        - { name: caseId, in: path, required: true, schema: { type: string } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [note], properties: { note: { type: string, maxLength: 4000 } } }
      responses:
        '201': { description: Note created and audit logged. }
  /v1/reports:
    get:
      tags: [Reports]
      summary: List draft regulatory reports
      responses:
        '200': { description: Tenant draft reports; not filings. }
    post:
      tags: [Reports]
      summary: Prepare an STR/SAR draft from a case
      description: Draft only. No direct regulator submission or deadline calculation.
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [caseId, reportType], properties: { caseId: { type: string }, reportType: { type: string, enum: [STR, SAR, other] }, jurisdiction: { type: string }, narrative: { type: string, maxLength: 6000 } } }
      responses:
        '201': { description: Draft created with a reporting-channel reference. }
  /v1/audit-events:
    get:
      tags: [Audit]
      summary: Read tenant audit events
      responses:
        '200': { description: Tenant-scoped append-only audit history; requires audit:read. }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: zco_live token; keys are shown once after approval
