openapi: 3.1.0
info:
  title: ScanSureX Online Pharmacy — Prescription Verification API
  version: 1.0.0
  description: |
    Verifies prescriptions issued through ScanSureX (doctor portal and Instant e-Prescription) for
    external online pharmacies. Call from your backend only: API keys must never reach a browser.

    Base URL (production): https://api.scansurex.com/api/v1/online-pharmacy  (the dispensary service)
    Authentication: `Authorization: Bearer ssx_live_…` (production) or `ssx_test_…` (sandbox).

    Results: DOCUMENT_VERIFIED (a ScanSureX PDF whose content matches the signed original),
    REFERENCE_VERIFIED (the reference is a genuine, valid prescription; the file itself is not proven),
    REJECTED (signature invalid, cancelled, expired, altered, or no ScanSureX code), REVIEW_REQUIRED
    (could not decide automatically, e.g. no readable QR on a photo), VERIFICATION_UNAVAILABLE (our failure).

    Patient permission: a ScanSureX QR token inside the uploaded file (the emailed PDF or a portal
    download) counts as the patient's permission and the prescription details are returned at once.
    A photo of the paper or a typed reference proves authenticity only; call the consent endpoints to
    have a one-time code sent to the patient, then confirm it to receive the details.

    Billing: `billable` and `charge_amount` describe the charge for this call. DOCUMENT_VERIFIED,
    REFERENCE_VERIFIED and REJECTED are billable; REVIEW_REQUIRED and VERIFICATION_UNAVAILABLE are not;
    sandbox calls never are; a retry with the same Idempotency-Key is never charged twice.
    Verification is not dispensing authority: `pharmacist_review_required` is always true.
servers:
  - url: /api/v1/online-pharmacy
security:
  - apiKey: []
components:
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: ssx_live_… / ssx_test_…
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema: { type: string, maxLength: 128 }
      description: Unique per logical request. A retry with the same key and payload returns the stored result (X-Idempotent-Replay true); the same key with a different payload is refused with 409.
  schemas:
    Verification:
      type: object
      properties:
        verification_id: { type: string, example: VER-2026-7F3A91C2 }
        environment: { type: string, enum: [sandbox, production] }
        verification_status: { type: string, enum: [DOCUMENT_VERIFIED, REFERENCE_VERIFIED, REJECTED, REVIEW_REQUIRED, VERIFICATION_UNAVAILABLE] }
        reason_code: { type: string, example: CONTENT_MATCHES_SIGNED }
        prescription_id: { type: [string, "null"], example: SSX-RX-HGWYKH }
        prescription_status: { type: [string, "null"], example: ISSUED }
        signature_valid: { type: [boolean, "null"] }
        document_integrity_verified: { type: boolean }
        doctor_registration_status: { type: [string, "null"], example: VERIFIED_AT_ISSUANCE }
        consent_required: { type: boolean }
        consent_method: { type: [string, "null"], enum: [QR_TOKEN, OTP, null] }
        billable: { type: boolean }
        charge_amount: { type: string, example: "2.00" }
        currency: { type: string, example: INR }
        pharmacist_review_required: { type: boolean, example: true }
        dispensing_authorised: { type: boolean, example: false }
        prescription:
          type: [object, "null"]
          description: Present only when the patient's permission is established (token or confirmed code).
          properties:
            prescription_ref: { type: string }
            issued_at: { type: string, format: date-time }
            expires_at: { type: string, format: date-time }
            status: { type: string }
            diagnosis: { type: string }
            doctor: { type: object, properties: { name: { type: string }, registration_no: { type: string }, council: { type: string }, speciality: { type: string }, registry_attestation: { type: [string, "null"] } } }
            patient: { type: object, properties: { name: { type: string }, date_of_birth: { type: [string, "null"] }, gender: { type: [string, "null"] }, allergies: { type: array, items: { type: string } } } }
            items: { type: array, items: { type: object, properties: { name: { type: string }, strength: { type: string }, dosage: { type: string }, frequency: { type: string }, duration_days: { type: integer }, quantity: { type: integer }, schedule: { type: string } } } }
            signature: { type: object, properties: { type: { type: string }, document_hash: { type: string }, verified: { type: boolean } } }
        verified_at: { type: string, format: date-time }
    Error:
      type: object
      properties:
        error: { type: string, example: API_KEY_INVALID }
        message: { type: string }
paths:
  /prescriptions/verify/upload:
    post:
      summary: Verify an uploaded prescription file (PDF, JPEG or PNG)
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema: { type: object, properties: { file: { type: string, format: binary } }, required: [file] }
      responses:
        "201": { description: Verification performed, content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } }
        "200": { description: Idempotent replay of an earlier verification }
        "401": { description: API key missing or invalid }
        "409": { description: Idempotency-Key reused with a different payload }
        "413": { description: File too large (default limit 10 MB) }
        "415": { description: Not a PDF, JPEG or PNG }
        "429": { description: Rate limit exceeded }
  /prescriptions/verify/reference:
    post:
      summary: Verify by prescription reference and/or a QR token your software decoded
      parameters: [ { $ref: "#/components/parameters/IdempotencyKey" } ]
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, properties: { reference: { type: string, example: SSX-RX-HGWYKH }, token: { type: string, description: The secret from a ScanSureX QR (…/rx#TOKEN or SCANSUREX-VERIFY|ref|TOKEN) } } }
      responses:
        "201": { description: Verification performed, content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } }
  /verifications/{id}:
    get:
      summary: Fetch a verification by id (VER-…)
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200": { description: The stored result, including details once consent is confirmed }
        "404": { description: Not found for this partner }
  /verifications/{id}/consent/request:
    post:
      summary: Send the patient a one-time code so details can be released
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      responses:
        "200": { description: Code sent, content: { application/json: { example: { consent_required: true, consent_id: "ck…", channel: "email", sent_to: "r***@gmail.com", expires_at: "2026-11-04T10:30:00Z" } } } }
        "422": { description: ScanSureX holds no contact for this patient }
  /verifications/{id}/consent/confirm:
    post:
      summary: Confirm the patient's code; returns the verification with details
      parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
      requestBody: { required: true, content: { application/json: { schema: { type: object, properties: { code: { type: string, pattern: "^\\d{6}$" } }, required: [code] } } } }
      responses:
        "200": { description: Details released, content: { application/json: { schema: { $ref: "#/components/schemas/Verification" } } } }
        "403": { description: Wrong code }
        "410": { description: No live code; request a new one }
        "429": { description: Too many wrong codes }
  /usage:
    get:
      summary: Usage and charges for a period (default last 30 days)
      parameters:
        - { name: from, in: query, schema: { type: string, format: date-time } }
        - { name: to, in: query, schema: { type: string, format: date-time } }
      responses: { "200": { description: Totals and a breakdown by status } }
  /verifications:
    get:
      summary: Recent verifications (newest first)
      parameters: [ { name: limit, in: query, schema: { type: integer, maximum: 200 } } ]
      responses: { "200": { description: List } }
  /api-keys:
    get:
      summary: This partner's API keys (prefixes only)
      responses: { "200": { description: List } }
  /sandbox/samples/{kind}:
    get:
      summary: Sandbox sample files (sandbox keys only)
      parameters: [ { name: kind, in: path, required: true, schema: { type: string, enum: [valid-email, valid-portal, valid-photo, expired, cancelled, tampered] } } ]
      responses: { "200": { description: A sample PDF or PNG to send to the upload endpoint } }
