> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinikehr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Start a patient import

> **Permission:** `patients:write` Import up to 100 patients from a FHIR Bundle, NDJSON, or a JSON array. Nothing is written unless every row is valid. The same import the app's Patients screen offers, for the clinic's OWN key. The file is checked in full before anything is written: every row goes through the same validation a real create does. If EVERY row is valid the import runs at once and the response is the finished job (`status: succeeded`). If ANY row fails, nothing is written and the response is the job with `status: needs_review` and a `row_errors` list — row number, field and reason for each, never the value that failed. Fix the file and send it again, or call `runValidPatientImportRows` to import only the rows that passed. A file with exactly the same content that has already been imported is refused `already_imported`, so retrying a timed-out call is safe; no Idempotency-Key is needed or read. An import contacts no patient and starts no automation. A possible duplicate is still created and counted in `flagged_duplicate`, for staff to review. Limits: 100 rows and 10 MiB, refused before any row is read. Needs `patients:write`, the API add-on and the accepted API data agreement. Only a clinic's own key may import — an organization connected to the clinic may not, whatever it was granted (`not_found`).



## OpenAPI

````yaml /openapi/ehr-api.v1.yaml post /v1/clinics/{clinic_id}/patient-imports
openapi: 3.1.0
info:
  title: ClinikEHR API
  version: 1.0.0
  description: >-
    Server-to-server REST API for a clinic's own data, reached with a key the
    clinic (or a developer workspace) creates in the developer portal.


    Authenticate every request with `Authorization: Bearer <key>`. Test keys
    reach only your workspace's own synthetic sandbox; live keys reach the
    clinic that created them. Lists wrap their rows as `{ data, has_more,
    next_cursor }`; single resources are returned directly. Errors are
    `application/problem+json` (RFC 9457) with a stable `code`.


    Write requests accept an `Idempotency-Key` header so a retry never repeats a
    change. See the guides for authentication, permissions, pagination, rate
    limits and retries.
servers:
  - url: https://api.clinikehr.com
security:
  - ApiKey: []
paths:
  /v1/clinics/{clinic_id}/patient-imports:
    post:
      tags:
        - Patients
      summary: Start a patient import
      description: >-
        **Permission:** `patients:write` Import up to 100 patients from a FHIR
        Bundle, NDJSON, or a JSON array. Nothing is written unless every row is
        valid. The same import the app's Patients screen offers, for the
        clinic's OWN key. The file is checked in full before anything is
        written: every row goes through the same validation a real create does.
        If EVERY row is valid the import runs at once and the response is the
        finished job (`status: succeeded`). If ANY row fails, nothing is written
        and the response is the job with `status: needs_review` and a
        `row_errors` list — row number, field and reason for each, never the
        value that failed. Fix the file and send it again, or call
        `runValidPatientImportRows` to import only the rows that passed. A file
        with exactly the same content that has already been imported is refused
        `already_imported`, so retrying a timed-out call is safe; no
        Idempotency-Key is needed or read. An import contacts no patient and
        starts no automation. A possible duplicate is still created and counted
        in `flagged_duplicate`, for staff to review. Limits: 100 rows and 10
        MiB, refused before any row is read. Needs `patients:write`, the API
        add-on and the accepted API data agreement. Only a clinic's own key may
        import — an organization connected to the clinic may not, whatever it
        was granted (`not_found`).
      operationId: startPatientImport
      parameters:
        - name: clinic_id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PatientImportRequest'
      responses:
        '200':
          description: The finished job — `succeeded`, `needs_review`, or `failed`.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                properties:
                  data:
                    $ref: '#/components/schemas/PatientImportJob'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          $ref: '#/components/responses/TooManyRequests'
      security:
        - ApiKey: []
components:
  schemas:
    PatientImportRequest:
      type: object
      additionalProperties: false
      required:
        - format
        - data
      properties:
        format:
          type: string
          enum:
            - json_array
            - ndjson
            - fhir_bundle
          description: How `data` is written.
        data:
          description: >-
            `json_array` — an array of patient objects in the same shape as
            `createPatient` (a row that carries `allergies`,
            `current_medications` or `chronic_conditions` is rejected by name
            like any other invalid row). `ndjson` — one string, a patient object
            per line. `fhir_bundle` — a FHIR Bundle whose entries include
            Patient resources (other resource types are ignored). At most 100
            rows.
          oneOf:
            - type: array
              minItems: 1
              maxItems: 100
              items:
                type: object
            - type: string
            - type: object
    PatientImportJob:
      type: object
      required:
        - id
        - status
        - input_format
        - row_count
        - last_processed_row
        - counts
        - row_errors
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
        status:
          type: string
          enum:
            - validating
            - needs_review
            - running
            - succeeded
            - failed
        input_format:
          type: string
          enum:
            - json_array
            - ndjson
            - fhir_bundle
        row_count:
          type: integer
        last_processed_row:
          type: integer
        counts:
          type: object
          required:
            - created
            - flagged_duplicate
            - skipped_already_imported
            - failed
          properties:
            created:
              type: integer
            flagged_duplicate:
              type: integer
              description: Created, and marked as a possible duplicate for staff to review.
            skipped_already_imported:
              type: integer
            failed:
              type: integer
        row_errors:
          type: array
          items:
            $ref: '#/components/schemas/PatientImportRowError'
        started_by:
          type: string
          enum:
            - key
            - session
        valid_only_run_at:
          type:
            - string
            - 'null'
          format: date-time
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    PatientImportRowError:
      type: object
      required:
        - row
        - field
        - reason
      description: Where a row failed and why. Never the value that failed.
      properties:
        row:
          type: integer
          minimum: 1
          description: 1-based position in the file.
        field:
          type: string
        reason:
          type: string
    Problem:
      type: object
      description: RFC 9457 application/problem+json.
      required:
        - type
        - title
        - status
        - code
        - request_id
      properties:
        type:
          type: string
          format: uri
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        code:
          type: string
        request_id:
          type: string
        errors:
          type: array
          items:
            type: object
            required:
              - pointer
              - message
            properties:
              pointer:
                type: string
              message:
                type: string
  responses:
    BadRequest:
      description: >-
        The request failed validation (unknown query parameter, limit out of
        range, malformed body).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unauthorized:
      description: >-
        Missing, malformed, unknown, revoked or expired key, or the wrong
        secret.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Forbidden:
      description: >-
        The key or connection lacks the required scope, the plan does not
        include this resource, the request came from an address this key's IP
        allow-list does not permit, a TEST-environment workspace key asked for a
        clinic_id other than its own workspace's sandbox (`sandbox_only`), or a
        LIVE workspace key asked for a real clinic while its workspace's
        verification has lapsed (`not_verified`) — the response never names
        which addresses ARE allowed, nor which clinic actually is the sandbox.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: The record is absent, or out of the key's scope — one message for both.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Conflict:
      description: >-
        The request is well-formed but conflicts with the CURRENT STATE of the
        record — a double-booked slot (`slot_taken`), a clinician's calendar
        blocked at that time (`out_of_office`), nobody eligible free
        (`no_provider_available`), an item that still has stock
        (`item_has_stock`), a ledger movement that cannot be covered
        (`insufficient_stock`, `expired_stock_not_transferable`), a note already
        signed or clinician-touched (`note_not_editable`), an Idempotency-Key
        already used for a different request or still in flight
        (`idempotency_key_conflict`, `idempotency_key_reused`), or an
        external-stock batch rejected as structurally invalid or a suspicious
        shrink versus the clinic's last accepted batch (`batch_rejected`), or an
        outside record that conflicts with what was already submitted or
        decided: the same `client_reference` sent again with different content
        (`client_reference_conflict`), or a submission a clinician has already
        decided (`submission_not_pending`), or an insurance write that conflicts
        with the clinic's setup or the coverage's state: the patient already has
        a coverage for that payer (`coverage_exists`, the existing coverage's id
        is in `detail` when the caller can see that patient), a payer the clinic
        has not enabled in Settings → Insurance (`payer_not_enabled`), or
        `coverage_active: true` sent through the API
        (`activation_requires_staff` — only clinic staff activate a coverage),
        or a change to an outside care provider the clinic added rather than an
        app (`care_provider_clinic_managed` — only the clinic can change or
        remove it).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    PayloadTooLarge:
      description: >-
        The request body is larger than the route accepts (`payload_too_large` —
        10 MiB for a patient import).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    TooManyRequests:
      description: Rate limited. See the Retry-After header.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: ehr_live_<keyId>_<secret> or ehr_test_<keyId>_<secret>

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.