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

# Create or update records

> Writes records to the catalog. Send either a JSON list of records (`Content-Type: application/json`) or a CSV file (`multipart/form-data`, field `file`, headers as in the CSV template).

Writes are **all-or-nothing**: if any record or row fails validation, nothing in the request is written and `errors` lists every problem.

**Modes**
- `upsert` (default): records in the request are inserted, or merged into the existing record with the same id. Records not in the request are left alone.
- `reconcile`: the request is treated as your **complete** catalog. Records in it are inserted or updated, and every active record not in it is deactivated (never hard-deleted, so it can be restored by sending it again). If a reconcile would deactivate more than half of a non-empty catalog, it is rejected with a `shrink_guard` error unless you pass `confirm_shrink=true`. An empty list or a CSV with no data rows is always rejected in this mode.

Use `dry_run=true` to validate and get the counts without writing anything.

For `type=lab_panel`, every id in `member_result_ids` must already be stored as a `lab_result` in your workspace, so write results first. A panel needs at least one member and an id can appear once. A member that is not a stored result is rejected with the message `<id> is not a result in this catalog`.

`overwrite=true` (CSV only) hard-deletes the whole catalog and replaces it with the file, in one transaction. It cannot be combined with `mode=reconcile`; prefer `reconcile`, which can be undone.



## OpenAPI

````yaml post /admin/datasets/v1/
openapi: 3.1.0
info:
  title: Eka Care Datasets API
  description: >-
    Manage your workspace's formulary catalogs (medications, lab investigations,
    lab results, lab panels, symptoms and diagnoses): upload them as JSON or
    CSV, keep them in sync, read them back, and switch on partner search in the
    Eka Medical Database. Every request acts only on the workspace your access
    token belongs to.
  version: 1.0.0
  contact:
    name: Eka Care Support
    url: https://eka.care/
    email: support@eka.care
servers:
  - url: https://api.eka.care
    description: Production
  - url: https://api.dev.eka.care
    description: Stage/Sandbox
security:
  - BearerAuth: []
tags:
  - name: datasets
paths:
  /admin/datasets/v1/:
    post:
      tags:
        - datasets
      summary: Create or update records
      description: >-
        Writes records to the catalog. Send either a JSON list of records
        (`Content-Type: application/json`) or a CSV file (`multipart/form-data`,
        field `file`, headers as in the CSV template).


        Writes are **all-or-nothing**: if any record or row fails validation,
        nothing in the request is written and `errors` lists every problem.


        **Modes**

        - `upsert` (default): records in the request are inserted, or merged
        into the existing record with the same id. Records not in the request
        are left alone.

        - `reconcile`: the request is treated as your **complete** catalog.
        Records in it are inserted or updated, and every active record not in it
        is deactivated (never hard-deleted, so it can be restored by sending it
        again). If a reconcile would deactivate more than half of a non-empty
        catalog, it is rejected with a `shrink_guard` error unless you pass
        `confirm_shrink=true`. An empty list or a CSV with no data rows is
        always rejected in this mode.


        Use `dry_run=true` to validate and get the counts without writing
        anything.


        For `type=lab_panel`, every id in `member_result_ids` must already be
        stored as a `lab_result` in your workspace, so write results first. A
        panel needs at least one member and an id can appear once. A member that
        is not a stored result is rejected with the message `<id> is not a
        result in this catalog`.


        `overwrite=true` (CSV only) hard-deletes the whole catalog and replaces
        it with the file, in one transaction. It cannot be combined with
        `mode=reconcile`; prefer `reconcile`, which can be undone.
      operationId: writeDatasetRecords
      parameters:
        - $ref: '#/components/parameters/Type'
        - name: mode
          in: query
          required: false
          description: >-
            `upsert` leaves records not in the request alone; `reconcile`
            deactivates them.
          schema:
            type: string
            enum:
              - upsert
              - reconcile
            default: upsert
        - name: dry_run
          in: query
          required: false
          description: Validate and count without writing.
          schema:
            type: boolean
            default: false
        - name: confirm_shrink
          in: query
          required: false
          description: Allow a reconcile that deactivates more than half of the catalog.
          schema:
            type: boolean
            default: false
        - name: overwrite
          in: query
          required: false
          description: >-
            CSV only. Replace the whole catalog with the file. Not allowed with
            `mode=reconcile`.
          schema:
            type: boolean
            default: false
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: array
              minItems: 1
              description: Records of the type named in `type`.
              items:
                oneOf:
                  - $ref: '#/components/schemas/Medication'
                  - $ref: '#/components/schemas/LabInvestigation'
                  - $ref: '#/components/schemas/LabResult'
                  - $ref: '#/components/schemas/LabPanel'
                  - $ref: '#/components/schemas/Symptom'
                  - $ref: '#/components/schemas/Diagnosis'
            example:
              - medication_id: DRUG1001
                name: Dolo 650
                strength: 650mg
                generic_name: Paracetamol
                form_name: Tablet
                manufacturer: Micro Labs Ltd
              - medication_id: DRUG1002
                name: Augmentin 625 Duo
                generic_list:
                  - Amoxicillin
                  - Clavulanic Acid
                form_name: Tablet
          multipart/form-data:
            schema:
              type: object
              required:
                - file
              properties:
                file:
                  type: string
                  format: binary
                  description: >-
                    CSV with one header row. Column names match the record
                    fields; multi-value cells are pipe-separated
                    (`Dolo|Paracetamol 650`), except `generic_list` and
                    `generic_list_ids`, which use `+`.
      responses:
        '200':
          description: Written (or, with `dry_run=true`, validated and counted).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WriteResponse'
              example:
                success: true
                message: 2 medication upserted
                data:
                  upserted: 2
                  inserted: 1
                  updated: 1
                  reactivated: 0
                  deactivated: 0
                  dry_run: false
        '400':
          description: >-
            Validation failed, the shrink guard stopped a reconcile, or the
            request was malformed. Nothing was written.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                validation:
                  summary: A record failed validation
                  value:
                    success: false
                    message: validation failed
                    errors:
                      - index: 1
                        field: name
                        msg: required
                        value: null
                shrinkGuard:
                  summary: Reconcile would deactivate most of the catalog
                  value:
                    success: false
                    message: >-
                      reconcile would deactivate 800 of 1000 active rows; pass
                      confirm_shrink=true to proceed
                    errors:
                      - code: shrink_guard
                        active_before: 1000
                        would_deactivate: 800
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
components:
  parameters:
    Type:
      name: type
      in: query
      required: true
      description: Which catalog to act on.
      schema:
        type: string
        enum:
          - medication
          - lab_investigation
          - lab_result
          - lab_panel
          - symptom
          - diagnosis
  schemas:
    Medication:
      type: object
      title: Medication
      required:
        - medication_id
        - name
      properties:
        medication_id:
          type: string
          maxLength: 200
          description: Your id for the drug.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
          description: Alternate names and short forms, used by search.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        strength:
          type: string
          maxLength: 32
        generic_name:
          type: string
          maxLength: 300
        generic_id:
          type: string
          maxLength: 64
        generic_list:
          type: array
          items:
            type: string
            maxLength: 100
          description: All generic (salt) components.
        generic_list_ids:
          type: array
          items:
            type: string
            maxLength: 64
        form_name:
          type: string
          maxLength: 64
          description: >-
            Dosage form. Matched case-insensitively against the [accepted drug
            forms](/docs/datasets/forms); a match links the drug to Eka's form,
            and any other value is stored as text without that link.
        sku:
          type: string
          maxLength: 64
        schedule_code:
          type: string
          maxLength: 32
        custom_type:
          type: string
          maxLength: 64
        therapy_class:
          type: string
          maxLength: 128
        therapy_class_id:
          type: string
          maxLength: 64
        action_class:
          type: string
          maxLength: 128
        action_class_id:
          type: string
          maxLength: 64
        manufacturer:
          type: string
          maxLength: 200
        otc:
          type: boolean
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    LabInvestigation:
      type: object
      title: LabInvestigation
      required:
        - investigation_id
        - name
      description: >-
        What a doctor orders or advises: a lab test, scan or package. The values
        that come back are `lab_result` records.
      properties:
        investigation_id:
          type: string
          maxLength: 200
          description: Your id for the investigation.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
        category:
          type: string
          maxLength: 64
        type:
          type: string
          maxLength: 64
        specimen:
          type: string
          maxLength: 100
        unit:
          type: string
          maxLength: 32
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    LabResult:
      type: object
      title: LabResult
      required:
        - result_id
        - name
      description: >-
        A value that comes back from an investigation, such as hemoglobin. It
        has one unit. Panels refer to results by `result_id`.
      properties:
        result_id:
          type: string
          maxLength: 200
          description: Your id for the result.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
        is_numerical:
          type: boolean
          description: True when the reading is a number.
        result_type:
          type: string
          enum:
            - 'N'
            - C
            - D
          description: N is numeric, C is categorical, D is descriptive.
        scale_type:
          type: string
          maxLength: 16
        loinc:
          type: string
          maxLength: 32
          description: LOINC code. Not checked against the LOINC vocabulary.
        unit:
          type: string
          maxLength: 32
          description: >-
            The one unit this result is reported in. Eka products show it only
            when `unit_id` is also set.
        unit_id:
          type: string
          maxLength: 64
          description: >-
            Eka's id for the unit. Without it the `unit` is not shown in Eka
            products.
        specimen:
          type: string
          maxLength: 100
        specimen_id:
          type: string
          maxLength: 64
          description: The specimen's id, served as is with the specimen.
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    LabPanel:
      type: object
      title: LabPanel
      required:
        - panel_id
        - name
        - member_result_ids
      description: >-
        A named, ordered collection of results that a doctor fills in, such as
        CBC. Every member must already be a `lab_result` in the same workspace
        and knowledge base, so write results first. Panel search can only be on
        while result search is on.
      properties:
        panel_id:
          type: string
          maxLength: 200
          description: Your id for the panel.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
        member_result_ids:
          type: array
          minItems: 1
          items:
            type: string
            maxLength: 200
          description: >-
            The `result_id` of each member result, in display order. Each id
            must be stored as a result, and can appear once. A member whose
            result is deactivated or deleted later stays on the panel and is
            skipped when the panel is served.
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    Symptom:
      type: object
      title: Symptom
      required:
        - symptom_id
        - name
      properties:
        symptom_id:
          type: string
          maxLength: 200
          description: Your id for the symptom.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
        snomed_id:
          type: string
          maxLength: 32
          description: SNOMED CT concept id. Not checked against the vocabulary.
        icd10:
          type: string
          maxLength: 32
        icd10_term:
          type: string
          maxLength: 255
        gender:
          type:
            - string
            - 'null'
          enum:
            - M
            - F
            - null
          description: Leave empty when the symptom applies to everyone.
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    Diagnosis:
      type: object
      title: Diagnosis
      required:
        - diagnosis_id
        - name
      properties:
        diagnosis_id:
          type: string
          maxLength: 200
          description: Your id for the diagnosis.
        name:
          type: string
          maxLength: 200
        ekaid:
          type:
            - string
            - 'null'
          maxLength: 64
          description: >-
            Optional. Eka's own id for this item, taken from Eka's reference
            list, so Eka products (for example EMR calculators) recognise it.
            Not checked against Eka's records; an id you send is stored as
            given. Re-uploading a CSV without this column keeps existing values;
            a blank cell in the column clears it. In a JSON update, leaving
            `ekaid` out or sending it empty keeps the current value.
        display_name:
          type: string
          maxLength: 200
          description: Shown to end users. Defaults to `name`.
        aliases:
          type: array
          items:
            type: string
            maxLength: 200
        snomed_id:
          type: string
          maxLength: 32
          description: SNOMED CT concept id. Not checked against the vocabulary.
        icd10:
          type: string
          maxLength: 32
        icd10_term:
          type: string
          maxLength: 255
        gender:
          type:
            - string
            - 'null'
          enum:
            - M
            - F
            - null
          description: Leave empty when the diagnosis applies to everyone.
        is_active:
          type: boolean
          description: Defaults to true on write.
        updated_at:
          type: string
          format: date-time
          readOnly: true
    WriteResponse:
      allOf:
        - $ref: '#/components/schemas/SuccessEnvelope'
        - type: object
          properties:
            data:
              type: object
              properties:
                upserted:
                  type: integer
                  description: inserted + updated + reactivated.
                inserted:
                  type: integer
                updated:
                  type: integer
                reactivated:
                  type: integer
                  description: Previously inactive records made active again.
                deactivated:
                  type: integer
                  description: Records deactivated by a reconcile. Always 0 in upsert mode.
                dry_run:
                  type: boolean
                batch_id:
                  type: integer
                  description: CSV uploads only. Matches `batch_id` in the batches list.
    ErrorResponse:
      type: object
      required:
        - success
        - message
      properties:
        success:
          type: boolean
          const: false
        message:
          type: string
        errors:
          type: array
          description: >-
            Present when validation failed, the shrink guard stopped a
            reconcile, or a search switch order was refused.
          items:
            oneOf:
              - $ref: '#/components/schemas/FieldError'
              - $ref: '#/components/schemas/ShrinkGuardError'
              - $ref: '#/components/schemas/SearchOrderError'
    SuccessEnvelope:
      type: object
      required:
        - success
        - message
      properties:
        success:
          type: boolean
          const: true
        message:
          type: string
    FieldError:
      type: object
      properties:
        index:
          type:
            - integer
            - 'null'
          description: >-
            For a JSON body, the 0-based position of the failing record in the
            list. For a CSV, the 1-based data row (the header row is not
            counted). `null` for a file-level error.
        field:
          type:
            - string
            - 'null'
        msg:
          type: string
        value:
          description: The rejected value.
    ShrinkGuardError:
      type: object
      properties:
        code:
          type: string
          const: shrink_guard
        active_before:
          type: integer
        would_deactivate:
          type: integer
    SearchOrderError:
      type: object
      properties:
        code:
          type: string
          enum:
            - turn_on_results_first
            - results_in_other_kb
            - turn_off_panels_first
  responses:
    Unauthorized:
      description: The access token is missing, invalid or expired.
    PayloadTooLarge:
      description: >-
        The request body is larger than 10 MB. Split the upload into several
        upsert requests.
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access token for your workspace.

````

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