Skip to main content

Patient Record Import API

<p class="callout success"><strong>API documentation:</strong> Updated for the public patient UUID response behaviour verified on 27 August 2026.</p>

<p class="callout info"><strong>Machine integration:</strong> The API finds or creates the patient synchronously and returns the stable public <code>patient_uuid</code> in the initial response. Clinical extraction, SNOMED resolution, anatomical mapping and audit then continue asynchronously.</p>

Open Clinical History provides a REST-style API for submitting patient history documents directly into the clinical-history processing pipeline.


The API endpoint is:

`history_api_import.phpphp`

For example:

`https://www.openclinicalhistory.org/demo/history_api_import.phpphp`

The API can:

  • -

    create or locate a patient

- return

the patient's stable public `patient_uuid` immediately

- receive unstructured clinical history text
-

queue the document for extraction

-

process the document through the Open Clinical History extraction pipeline

-

monitor processing status

-

automatically audit and commit safe clinical events

-

manually initiate the audit/commit stage

-

inspect and manage the ingest queue


The API uses the **same clinical processing pipeline and safety rules as the interactive patient-history importerimporter**.


---

## API Processing Flow


A typical API import follows this sequence:

```text
External Clinical System
|
| POST patient + document
v
history_api_import.php
|
v
Find/Create Patient
|
+---- patient_uuid available
|
v
Store Source Document
|
v
Ingest Queue
|
+---- HTTP 202 Accepted
| document_id
| patient_uuid
| patient_url
| queue_id
| status_url
|
v
queue_worker.php
|
v
Clinical Extraction
|
v
SNOMED Resolution
|
v
Anatomical Mapping
|
v
Clinical Audit
|
+---------------------------+
| |
v v
Safe event Needs review
| |
v v
Committed Unmatched/Learning Queue
|
v
Patient Clinical History
```


Processing after submission is asynchronous.


A successful import request normally means:

> The patient has been found or created, the source document has been stored, and the document has been accepted and queued for processing.

It does not mean extraction has already completed.


Patient creation or lookup happens synchronously before the document is handed to the asynchronous extraction pipeline. The successful import response therefore includes the patient's stable public `patient_uuid` immediately, even though clinical extraction may still be queued or running.

The sending system should store this `patient_uuid` as its Open Clinical History patient reference. `document_id` remains the identifier for the individual imported source document.

The caller should use the returned status URL`status_url` to monitor progress.processing.


---

## Prerequisites


Before using the API:

  1. 1.

    Open Clinical History database migrations must be complete.

2.

The API must be enabled in **Admin → ConfigurationConfiguration**.

3.

At least one API token must have been created.

4.

The required LLM configuration must be available.

5.

The SNOMED and image/SNOMED databases should have been built.

6.

If the ingest queue is enabled, `queue_worker.phpphp` must be running.


Relevant configuration settings include:
api_enabled
api_require_https
- api_default_auto_commit`api_enabled`
api_max_upload_mb
- queue_enabled`api_require_https`
queue_max_attempts
- queue_job_timeout`api_default_auto_commit`
queue_lease_seconds
- `api_max_upload_mb`

-

`queue_enabled`

- `queue_max_attempts`
- `queue_job_timeout`
- `queue_lease_seconds`

---

## Authentication


Every API request requires an Open Clinical History API token.


Tokens are created under:


**Admin → Configuration → API TokensTokens**


The preferred authentication method is an HTTP Bearer token:

`Authorization: Bearer och_your_token_hereoch_your_token_here`

A fallback header is also supported:

`X-API-Token: och_your_token_hereoch_your_token_here`

Bearer authentication should be preferred.


### Token security


API tokens are secrets.


They should:

  • -

    never be embedded in publicly accessible client-side code

-

never be committed to source control

-

only be transmitted over HTTPS

-

be stored using the secret-management facilities of the integrating system

-

be revoked immediately if exposed


Open Clinical History stores only the SHA-256 hash of the token. The full plaintext token is displayed only once when it is created.


---

## API Scopes


Tokens can carry three scopes.
| Scope Scope| Purpose |
| --- | import--- |
| `import` | Submit patient history documents
|
| status`status` | Read document, processing and queue status
|
| commit`commit` | Run the clinical audit/commit process
|

A normal integration using automatic commit generally requires:requires
importthe status`import`, commit`status` 
and

`commit` scopes.


If auto_commit`auto_commit` is enabled for an import, the calling token must**must** have the commit`commit` scope as well as import`import`.


A token without the required scope receives:

`HTTP 403403`

with:

```json
{
"ok": false,
"error": {
"code": "insufficient_scope",
"message": "This token does not carry the required scope."
}
}
```


---

## HTTPS


By default:

`api_require_https = enabledenabled`

Requests made without HTTPS are rejected.


The API recognises HTTPS through:

  • -

    the PHP HTTPS server variable

-

server port 443

-

`X-Forwarded-Proto: https

https`

The latter allows Open Clinical History to operate correctly behind an HTTPS reverse proxy.


HTTPS should remain mandatory for environments containing clinical information.


---

## Response Format


Every API response uses JSON.


Successful response:

```json
{
"ok": true,
"data": {}
}
}
```

Error response:

```json
{
"ok": false,
"error": {
"code": "error_code",
"message": "Description of the error."
}
}
```


Applications should check both:

  1. 1.

    the HTTP status code; and

2.

the ok`ok` property.


---


## API Endpoints


The following actions are available.
| Method Method| Action | Scope | Purpose |
| --- | --- | --- | --- |
| GET
ping| `ping` | Valid token | Test authentication and API availability |
| POST import| import`import` | `import` | Submit a patient history
|
| GET status| status`status` | `status` | Monitor extraction/import progress
|
| GET document| status`document` | `status` | Retrieve the document processing summary
|
| POST commit| commit`commit` | `commit` | Audit and commit extracted events
|
| GET queue| status`queue` | `status` | Inspect the ingest queue
|
| POST queue| status`queue` | `status` + import`import` | Retry or cancel queue work
|
It is recommended that callers always specify action`action` explicitly.


---

## Testing the Connection


Use:

`GET history_api_import.php?action=pingping`

Example:

```bash
curl \
-H "Authorization: Bearer och_your_token_here" \
"https://www.openclinicalhistory.org/demo/history_api_import.php?action=ping"
```


A successful response resembles:

```json
{
"ok": true,
"data": {
"build": "2026-08-22.api-v2-app-config-llm",
"token": {
"id": 4,
"label": "Clinical integration",
"scopes": [
"import",
"status",
"commit"
]
},
"date_format": "dd/mm/yyyy",
"queue": {
"enabled": true,
"depth": {
"queued": 0,
"running": 1,
"done": 42,
"failed": 0,
"cancelled": 0
}
},
"server_time": "2026-08-25T19:45:00+10:00"
}
}
```


This provides a useful initial connectivity test because it confirms:

  • -

    the API is enabled

-

HTTPS is accepted

-

the token is valid

-

the token's available scopes

-

the configured date format

-

whether queue processing is enabled


---


## Importing a Patient Record


Use:

`POST history_api_import.php?action=importimport`

The preferred request format is:

`Content-Type: application/jsonjson`

Example request:

```json
{
"record_number": "MRN-1001",
"display_name": "Example Patient",
"dob": "1967-04-02",
"sex": "female",
"source_name": "hospital-discharge-summary.txt",
"text": "Patient clinical history goes here...",
"auto_commit": true,
"external_ref": "hospital-a:MRN-1001:discharge:84721"
}
```


---

## Import Fields

record_number


Required

###

`record_number`


**Required**

Example:

`"record_number": "MRN-1001"`

Maximum length:

`64 characterscharacters`

This is the primary external patient identifier used by the patient-import API.


Open Clinical History searches for an existing patient where:

```text
patient_record_number = supplied record_number
source_system = history_import
```


If found, that patient is reused.


If not found, a new patient is created.


#### Important


The API is currently a **find-or-create patient APIAPI**, not a demographic-update API.


If the patient already exists, supplying different:

  • -

    display name

- DOB

DOB

- sex

sex

does not**not** update the existing patient record.


The record_number`record_number` should therefore be stable for the lifetime of the patient within the source integration.


display_name

---

Optional


### `display_name`

**Optional**

Example:

`"display_name": "Example Patient"`

Used as the patient's human-readable display name when a new patient is created.


It is stored with the patient's attributes.


It is not used as the primary patient-matching key.


dob

---

Optional


### `dob`

**Optional**

Preferred format:

`YYYY-MM-DDDD`

Example:

`"dob": "1967-04-02"`

ISO dates are recommended for all machine integrations.


The API also accepts the date format configured under:


**Admin → Configuration → Date formatformat**


For example:

02/04/1967

when the system is configured for `dd/mm/yyyyyyyy`.


#### Validation


The date:

  • -

    must be valid

-

cannot be in the future

-

cannot have a year earlier than 1880


An invalid value produces:
bad_dob
`bad_dob`


Machine integrations should always use ISO `YYYY-MM-DDDD` to avoid regional date ambiguity.


sex

---

Optional


### `sex`

**Optional**

Accepted values are:
female
male
`female`
intersex
unknown
`male`

`intersex`

`unknown`

Values are case-insensitive.


If omitted:
unknown
`unknown`


is used.


An unrecognised value is currently normalised to:
unknown
`unknown`


rather than causing the request to fail.


source_name

---

Optional


### `source_name`

**Optional**

Identifies the source document.


Example:

`"source_name": "cardiology-letter-2026-08-25.txt"`

Default:

api-upload.txt

A meaningful source name is strongly recommended because it makes individual documents easier to identify in Open Clinical History.


text

---


### `text`

**Required for JSON importsimports**


Contains the complete unstructured source document.


Example:

`"text": "12 March 2018 - Patient admitted with..."`

The document can contain raw clinical text.


It does not need to be pre-classified into clinical events or SNOMED concepts.


Open Clinical History performs that processing.


The document must not be empty.


auto_commit

---

Optional


### `auto_commit`

**Optional**

Boolean:

`"auto_commit": truetrue`

or:

`"auto_commit": falsefalse`

If omitted, the value comes from:
api_default_auto_commit
`api_default_auto_commit`


in **Admin → ConfigurationConfiguration**.


When true`true`, the API token must have the:
commit
`commit`


scope.


#### What auto-commit actually means


Auto-commit does not**not** mean every extracted event is blindly inserted into the patient's clinical history.


After extraction, Open Clinical History performs its normal audit checks.


An event is automatically committed only when it satisfies the safety criteria.


Among other things, a proposal must:

  • -

    pass its clinical audit

-

have a resolved SNOMED concept

-

have no unresolved mapping warnings

-

not represent a negated statement

-

not represent family history

-

not be merely a historical-summary statement


Safe events are committed.


Events that cannot be safely committed are rejected from automatic commit and routed to the unmatched/learning workflow for further resolution.


Conceptually:

Extracted proposal

|

v

Clinical audit

|

+---- safe -----------------> Commit

|

+---- uncertain/unsafe -----> Review/Learning Queue

This allows unattended imports without treating every AI-generated proposal as trusted clinical data.


external_ref

---


### `external_ref`

**Optional but strongly recommendedrecommended**


Example:

`"external_ref": "hospital-a:MRN-1001:discharge:84721"`

This provides request-level idempotency.


If the same import request is retried with the same external_ref`external_ref`, Open Clinical History does not create another import.


Instead, it returns the existing document and its current processing state.


The response includes:

`"duplicate": truetrue`

and returns HTTP:

200 OK

rather than creating a second queue item.


Because duplicate responses use the existing document state, they also return the existing `patient_uuid` and `patient_url`. A retry can therefore recover the Open Clinical History patient identifier even if the caller never received the original `202 Accepted` response.

This is particularly important when an integration cannot determine whether an earlier HTTP request reached the server.
#### Recommended external reference design


Use a stable source-system identifier such as:

<system>:<patient>:<document-id>

For example:

hospital-a:MRN-1001:document-84721

or:

pms:48392:consultation-998312

The maximum stored length is approximately:

`190 characterscharacters`

The current implementation searches external_ref`external_ref` globally rather than per token, so integrations should ensure that external references are **globally unique across Open Clinical HistoryHistory**.


---

### Optional priority`priority`


The JSON import endpoint also accepts:

`"priority": 00`

This controls ordering within the ingest queue.


Allowed effective values are:

`-10 to +1010`

Higher numbers are processed before lower numbers.


For example:

`"priority": 55`

will be processed before a queued document with:

`"priority": 00`

Within the same priority, older queue items are processed first.


For normal integrations, leave this at:
0

unless there is a genuine requirement for priority processing.


---

## Example JSON Import

```bash
curl -X POST \
"https://www.openclinicalhistory.org/demo/history_api_import.php?action=import" \
-H "Authorization: Bearer och_your_token_here" \
-H "Content-Type: application/json" \
-d '{
"record_number": "MRN-1001",
"display_name": "Example Patient",
"dob": "1967-04-02",
"sex": "female",
"source_name": "specialist-letter.txt",
"text": "Clinical document contents...",
"auto_commit": true,
"external_ref": "hospital-a:MRN-1001:letter-84721"
}'
```


---

## Successful Import Response


When queue processing is enabled, a successful import normally returns:

`HTTP 202 AcceptedAccepted`

with a response similar to:

```json
{
"ok": true,
"data": {
"document_id": 142,
"patient_id": 27,
"patient_uuid": "5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
"patient_url": "/patient.php?p=5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
"queue_id": 81,
"queue_position": 2,
"state": "queued",
"auto_commit": true,
"external_ref": "hospital-a:MRN-1001:letter-84721",
"status_url": "/demo/history_api_import.php?action=status&doc=142",
"poll_after": 15
}
}
```

document_id


The patient identifier is available at this point even though the clinical history has not yet finished processing.

### `document_id`

Open Clinical History identifier for the uploaded source document.

Keep

Store this identifier whenfor monitoringdocument-level thestatus import.monitoring,
troubleshooting

patient_id

and

reconciliation.


---

### `patient_id`

Internal numeric Open Clinical History database identifier.

This may refer to:

- an existing patient matched by `record_number`; or
- a newly created patient.

`patient_id` is primarily an implementation identifier. External integrations should normally persist `patient_uuid` instead.

---

### `patient_uuid`

Stable public Open Clinical History patient identifier.


Example:

```text
5ab5c79f-a20d-11f1-bdaf-c6baf601d317
```

This mayis referthe to:identifier
    used by

    the patient viewer and is intended to be stored by the sending system as the Open Clinical History reference for that patient.


    It is returned immediately in the original `202 Accepted` import response. The caller does **not** need to wait for extraction or commit to finish before recording it.

    For an existing patientpatient, matchedthe byAPI record_number;returns orthat patient's existing

    UUID. For a newly created patient, the database creates the UUID during patient insertion and the API returns it before queue processing begins.


    The `patient_uuid` is also returned by:

    - duplicate/idempotent import responses
    - document status responses
    - the `document` endpoint
    - successful manual commit responses
    - non-queue import responses

    The current import request still matches patients using `record_number`; `patient_uuid` is a returned Open Clinical History identifier, not currently an import matching field.

    ---

    ### `patient_url`

    Relative URL for opening the patient's Open Clinical History viewer.

    Example:

    ```text
    /patient.php?p=5ab5c79f-a20d-11f1-bdaf-c6baf601d317
    ```


    This

    queue_id

    URL

    is derived from `patient_uuid`.


    Integrating systems may store it as a convenience, but `patient_uuid` should be treated as the durable identifier rather than parsing the URL.

    ---

    ### `queue_id`

    Identifier of the ingest queue item.


    queue_position

    This

    is useful for queue administration and troubleshooting but is not the patient or document identifier.


    ---

    ### `queue_position`

    Number of queued items currently ahead of this item.


    Therefore:

    ```text
    0
    ```


    means no queued items are ahead of it.


    Queue positions can change while workers process other records.


    state

    ---


    ### `state`

    Immediately after queue submission:

    ```text
    queued
    ```


    is normally returned.


    status_url

    ---


    ### `status_url`

    Relative URL that can be used to monitor the document.


    Example:

    ```text
    /demo/history_api_import.php?action=status&doc=142
    ```


    Authentication is still required when using this URL.


    poll_after

    ---


    ### `poll_after`

    Suggested number of seconds before polling again.


    In queue mode the API currently returns:

    ```text
    15
    ```


    seconds.


    Clients should respect this value rather than aggressively polling the server.


    ---

    ## Importing a File


    The API also supports:supports
    `multipart/form-datadata`.

    Use a file part named:named
    history`history`.

    For example:

    ```bash
    curl -X POST \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=import" \
    -H "Authorization: Bearer och_your_token_here" \
    -F "record_number=MRN-1001" \
    -F "display_name=Example Patient" \
    -F "dob=1967-04-02" \
    -F "sex=female" \
    -F "auto_commit=true" \
    -F "external_ref=hospital-a:MRN-1001:document-84721" \
    -F "history=@discharge-summary.txt"
    ```

    When a file is uploaded, its filename becomes the document's source_name`source_name`.


    ---

    ## Maximum Document Size


    The maximum accepted document size is controlled by:
    api_max_upload_mb
    `api_max_upload_mb`


    under:


    **Admin → ConfigurationConfiguration**


    Default:

    `2 MBMB`

    The limit applies to both:

    • -

      JSON text

      `text`
    -

    multipart history`history` files


    Documents exceeding the limit receive:

    `HTTP 413 Payload Too LargeLarge`

    with error code:
    too_large
    `too_large`


    ---

    ## Character Encoding


    Clinical text is expected to be UTF-8.


    If uploaded text is not valid UTF-8, the current importer attempts to convert it from:

    `ISO-8859-11`

    to UTF-8 before storing it.


    Integrating systems should supply UTF-8 directly wherever possible.


    ---

    ## Duplicate Source Documents


    Open Clinical History also calculates a SHA-256 hash of the normalised document text.


    For a given patient, if identical document content has already been stored, the existing document can be reused rather than storing another physical copy.


    However:

    external_ref

    > `external_ref` should still be used as the integration's primary idempotency mechanism.

    The document hash protects against identical content. external_ref`external_ref` identifies the source-system transaction/document itself.


    ---

    ## Monitoring an Import


    Use:

    `GET history_api_import.php?action=status&doc=<document_id>`

    Requires:
    status
    `status`


    scope.


    Example:

    ```bash
    curl \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=status&doc=142"
    ```


    ---

    ## Import States


    The high-level API state`state` may be:
    | State State| Meaning |
    | --- | queued--- |
    | `queued` | Waiting for an ingest worker
    |
    | extracting`extracting` | Clinical extraction is running
    |
    | extracted`extracted` | Extraction finished; automatic commit was not requested
    |
    | committed`committed` | Audit/commit processing has completed
    |
    | failed`failed` | Processing failed after available attempts
    |
    | cancelled`cancelled` | Queue work was manually cancelled
    |

    A typical automatic-import sequence is:

    ```text
    queued
    |
    v
    extracting
    |
    v
    committed
    ```


    Without auto-commit:

    ```text
    queued
    |
    v
    extracting
    |
    v
    extracted
    ```


    The client can then explicitly call the commit`commit` endpoint.


    ---

    ## Example Status Response


    A completed status response can resemble:

    ```json
    {
    "ok": true,
    "data": {
    "document_id": 142,
    "patient_id": 27,
    "patient_uuid": "5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
    "patient_url": "/patient.php?p=5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
    "source_name": "specialist-letter.txt",
    "document_state": "committed",
    "status_note": null,
    "char_count": 18492,18473,
    "state": "committed",
    "queue": {
    "id": 81,
    "state": "done",
    "position": 0,
    "attempts": 1,
    "max_attempts": 3,
    "available_at": "2026-08-25 19:40:10",
    "started_at": "2026-08-25 19:40:12",
    "finished_at": "2026-08-25 19:41:44",
    "last_error": null
    },
    "auto_commit": true,
    "external_ref": "hospital-a:MRN-1001:letter-84721",
    "message": null,
    "job": "7a2d4c9e14f0b581",
    "extraction": {
    "state": "done",
    "phase": "complete",
    "chunks_total": 5,
    "chunks_done": 5,
    "events": 19,
    "requests": 7,
    "tokens": 14231,
    "started": 1787643612,
    "updated": 1787643704,
    "errors": []
    },
    "proposals": {
    "proposed": 0,
    "committed": 17,
    "rejected": 2
    },
    "committed": 17,
    "queued": 2,
    "status_url": "/demo/history_api_import.php?action=status&doc=142"
    }
    }
    ```


    The exact values depend on the document and current pipeline stage.


    The `patient_uuid` remains the same throughout the lifecycle of the import. A client that did not persist the UUID from the initial import response can recover it from `status` or `document`.

    ---

    ## Understanding the Status Response

    document_state


    ### `patient_uuid`

    Stable public Open Clinical History identifier for the patient associated with this document.

    This value can be persisted by the source system and used to correlate future Open Clinical History links or references to the same patient.

    ---

    ### `patient_url`

    Relative viewer URL constructed from `patient_uuid`.

    ---

    ### `document_state`

    The current status stored against the source document itself.


    This is distinct from the high-level API state`state`.


    For normal integrations, state`state` should generally be used to decide what action to take next.


    char_count

    ---


    ### `char_count`

    Number of characters in the stored source document.


    queue

    ---


    ### `queue`

    Contains ingest-queue information when queue mode is enabled.


    #### `queue.statestate`


    Possible queue-level states are:
    queued
    running
    `queued`
    done
    failed
    `running`
    cancelled
    `done`

    attempts


    `failed`

    `cancelled`

    #### `attempts`

    Number of times a worker has claimed the queue item.


    A worker claim counts as an attempt even if that worker subsequently disappears.

    max_attempts


    #### `max_attempts`

    Maximum number of processing attempts permitted before the item is permanently parked as failed.

    last_error


    #### `last_error`

    The most recent queue processing error, if any.


    A job that fails and is automatically retried may therefore have a last_error`last_error` while returning to:
    queued
    `queued`


    ---

    ## Extraction Progress


    The extraction`extraction` object provides lower-level information about the extraction worker.


    It may contain:
    state
    phase
    - chunks_total`state`
    chunks_done
    - events`phase`
    requests
    - tokens`chunks_total`
    started
    - updated`chunks_done`
    errors
    - `events`

    - `requests`
    - `tokens`
    - `started`
    - `updated`
    - `errors`

    This can be used to provide richer progress information to an integrating application.


    The extraction`extraction` object may be:be
    null`null`

    before a worker job has been created.


    ---

    ## Proposal Counts


    The status response contains:

    ```json
    {
    "proposals": {
    "proposed": 0,
    "committed": 17,
    "rejected": 2
    }
    }

    ```

    These are the extracted clinical event proposals associated with the document.

    proposed


    #### `proposed`

    Events that still await a final decision.

    committed


    #### `committed`

    Events accepted into the patient's clinical history.

    rejected


    #### `rejected`

    Events not automatically committed.


    Rejected does not necessarily mean that the extracted clinical statement was incorrect.


    It may mean that Open Clinical History could not safely establish:

    • -

      an appropriate SNOMED concept

    -

    sufficient specificity

    -

    an appropriate anatomical mapping

    -

    a clean audit result


    Such events can be routed through the unmatched/learning process.


    ---

    ## A Note About the Top-Level queued`queued` Value


    There are two different queue concepts in the API response.

    queue


    ### `queue`

    The object:

    ```json
    {
    "queue": {
    "state": "running"
    }
    }

    ```

    describes the **document ingest queuequeue**.

    queued


    ### `queued`

    The top-level numeric value:

    `"queued": 22`

    after the audit stage means that two extracted clinical events were routed to the **unmatched/learning workflowworkflow** rather than automatically committed.


    It does not**not** mean there are two documents ahead of this document in the ingest queue.


    ---

    ## Status by Job Identifier


    Status can also be requested using the extraction job identifier:

    `GET history_api_import.php?action=status&job=<job>`

    Example:

    ```bash
    curl \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=status&job=7a2d4c9e14f0b581"
    ```


    Job identifiers are 16 lowercase hexadecimal characters.


    For most integrations, monitoring by:by
    document_id`document_id` 

    is simpler.



    Status responses resolved from a job identifier also include the associated `patient_uuid` and `patient_url` once the document is identified.

    ---

    ## Document Endpoint


    Use:

    `GET history_api_import.php?action=document&doc=<document_id>`

    Requires:
    status
    `status`


    scope.


    Example:

    ```bash
    curl \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=document&doc=142"
    ```


    The current implementation returns the same assembled document-processing view used by document-based status requests.


    That view includes `patient_id`, `patient_uuid` and `patient_url`, so the Open Clinical History patient reference can be recovered from a known `document_id`.

    This endpoint provides a semantic way for an integration to request the current summary for a known document.


    ---

    ## Manual Commit


    If the document was imported with:

    ```json
    "auto_commit": false
    ```


    the normal completed state is:

    ```text
    extracted
    ```


    To run the audit and commit stage:

    ```text
    POST history_api_import.php?action=commit&doc=<document_id>
    ```

    Requires:


    Requires the:

    ```text
    commit
    ```


    scope.


    Example:

    ```bash
    curl -X POST \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=commit&doc=142"
    ```


    A successful response resembles:

    ```json
    {
    "ok": true,
    "data": {
    "document_id": 142,
    "patient_id": 27,
    "patient_uuid": "5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
    "patient_url": "/patient.php?p=5ab5c79f-a20d-11f1-bdaf-c6baf601d317",
    "state": "committed",
    "committed": 17,
    "queued": 2,
    "errors": []
    }
    }
    ```


    Again:
    committed
    `committed`

    is the number of clinical events accepted into the clinical history.

    queued
    `queued`

    is the number routed to the unmatched/learning workflow.



    The returned `patient_uuid` is the same stable public patient identifier supplied during the original import.

    ---

    ## Commit While Extraction Is Running


    The API will not allow a document to be committed while extraction is still active.


    It returns:

    `HTTP 409 ConflictConflict`

    with:
    still_extracting
    `still_extracting`


    The response includes a status_url`status_url` that should be polled until extraction has finished.


    ---

    ## Ingest Queue


    When:

    `queue_enabled = truetrue`

    API imports are placed into the controlled ingest queue.


    This prevents a burst of API submissions from launching an unlimited number of LLM extraction processes.


    The queue worker processes them in a controlled sequence.


    ---

    ## Inspecting the Queue


    Use:

    `GET history_api_import.php?action=queuequeue`

    Requires:
    status
    `status`


    scope.


    Example:

    ```bash
    curl \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=queue"
    ```


    The response includes:

    • -

      queue depth by state

    -

    recent queue items


    By default the most recent:recent
    25`25` 

    items are returned.


    A custom limit can be supplied:

    `?action=queue&limit=5050`

    The API permits between:

    `1 and 100100`

    items through this endpoint.


    ---

    ## Inspecting One Queue Item


    Use:

    `GET history_api_import.php?action=queue&id=<queue_id>`

    For example:

    ```bash
    curl \
    -H "Authorization: Bearer och_your_token_here" \
    "https://www.openclinicalhistory.org/demo/history_api_import.php?action=queue&id=81"
    ```


    The response includes the queue item and its current position.


    ---

    ## Retry a Failed Queue Item


    A failed or cancelled item can be reset and submitted again.


    Use:

    `POST history_api_import.php?action=queue&id=<queue_id>&op=retryretry`

    Requires both:
    status
    import
    `status`

    `import`

    scopes.


    Retry:

    • -

      changes the item back to queued

      `queued`
    -

    resets attempts to zero

    -

    makes the item immediately available

    -

    clears the previous worker lease

    -

    clears the previous queue error


    ---


    ## Cancel a Queue Item


    Use:

    `POST history_api_import.php?action=queue&id=<queue_id>&op=cancelcancel`

    Requires:
    status
    import
    `status`

    `import`

    scopes.


    Cancellation is allowed when the queue item is:
    queued
    `queued`


    or:
    failed
    `failed`


    A currently running worker is not cancelled by this endpoint.


    ---

    ## Automatic Queue Retries


    If processing fails, the queue automatically retries until:until
    queue_max_attempts`queue_max_attempts`

    has been reached.


    Retry delay uses increasing quadratic backoff.


    Conceptually, with successive failed attempts:

    `attempt 1 -> wait 60 secondsseconds`

    `attempt 2 -> wait 240 secondsseconds`

    `attempt 3 -> wait 540 secondsseconds`

    The delay is capped at one hour.


    Once all allowed attempts have been exhausted, the item is marked:
    failed
    `failed`


    ---

    ## Worker Leases


    When a worker claims a queue item, it receives a temporary lease.


    The lease is controlled by:by
    queue_lease_seconds`queue_lease_seconds`.

    Workers extend their lease while long-running extraction is taking place.


    If a worker crashes and its lease expires, the item can be returned to the queue rather than remaining permanently stuck in:
    running
    `running`


    ---

    ## Recommended Client Workflow


    A robust integration should use the following pattern:

    ```text
    1. Generate stable external_ref
    |
    v
    2. POST action=import
    |
    +---- HTTP 202 -----------------------------+
    | |
    v |
    Save patient_uuid |
    Save document_id |
    and
    Save status_url |
    Save queue_id if queue administration is needed |
    | |
    |
    v |
    3. Wait poll_after |
    | |
    v |
    4. GET action=status |
    |
    |
    +---- queued ------+-> continue polling
    |
    +---- extracting --+-> continue polling
    |
    +---- committed ----------> Complete
    |
    +---- extracted ----------> POST commit if required
    |
    +---- failed -------------> Investigate/retry
    |
    +---- cancelled ----------> Stop/re-submit if appropriate
    ```


    The critical distinction is:

    - `patient_uuid` identifies the patient in Open Clinical History.
    - `document_id` identifies this particular imported source document.
    - `external_ref` identifies the source-system submission/document for idempotency.
    - `queue_id` identifies the temporary ingest work item.

    Do not continuously resubmit the original document while waiting.


    Poll the status endpoint instead.


    If network uncertainty makes a resubmission necessary, send the **same external_ref`external_ref`**.
    If

    the original request was already accepted, the duplicate response returns the existing document state and `patient_uuid`.


    ---

    ## Recommended Polling Behaviour


    After submission, use the returned:

    `"poll_after": 1515`

    value.


    A simple client strategy is:

    ```text
    Submit
    |
    v
    wait 15 seconds
    |
    v
    check status
    |
    v
    still processing?
    |
    +-- yes --> wait again
    |
    +-- no --> process result
    ```


    There is no need to poll every second.


    Clinical extraction may involve several model requests and can take some time for large documents.


    ---

    ## Idempotent Retry Example


    Initial request:

    ```json
    {
    "record_number": "MRN-1001",
    "text": "Clinical history...",
    "external_ref": "hospital-a:document-84721",
    "auto_commit": true
    }
    ```


    Suppose the client's connection fails before it receives the response.


    The client may safely resend:

    ```json
    {
    "record_number": "MRN-1001",
    "text": "Clinical history...",
    "external_ref": "hospital-a:document-84721",
    "auto_commit": true
    }
    ```


    If the original submission was already accepted, the API returns the existing request with:

    `"duplicate": truetrue`

    rather than intentionally creating another import.


    ---

    ## Patient Matching Behaviour


    Patient matching deserves particular attention when designing an integration.


    Open Clinical History currently matches imported patients using:
    patient_record_number
    `patient_record_number`

    `+`

    `source_system = history_importhistory_import`

    It does not**not** currently match using:

    • -

      display name

    - DOB

    DOB

    - sex
    -

    sex

    document source

    -

    external reference


    Therefore:

    Therefore:


    `MRN-10011001`

    must consistently refer to the same patient.


    ### Existing patient


    If the patient already exists:exists,
    reusethe patientAPI 
    reuses

    that patient.


    ### New record number


    If it does not exist:exist,
    createthe API creates a new patientpatient.

    Consequently, changing a patient's external record number can create another Open Clinical History patient rather than updating the existing one.


    ### Relationship between `record_number` and `patient_uuid`

    These identifiers serve different purposes:

    | Identifier | Owner / purpose |
    | --- | --- |
    | `record_number` | Stable patient key supplied by the integrating source system and currently used by the import API to find or create the patient |
    | `patient_id` | Internal numeric Open Clinical History database identifier |
    | `patient_uuid` | Stable public Open Clinical History patient identifier returned to the source system |

    For subsequent imports for the same patient, the source system should continue to send the same `record_number`.

    The source system should also retain the returned `patient_uuid` so it has a durable Open Clinical History reference for that patient.

    The current API does not require or accept `patient_uuid` as the patient matching key on import.

    ---

    ## Patient Asset Set


    When a patient is first created by this API:API,
    `sex = malemale` 

    selects the:

    the
    male`male` 

    anatomical asset set.


    The current implementation uses the:
    female
    `female`


    asset set for the other sex values.


    This affects the anatomical visual layers used when displaying the patient's history.


    ---

    ## Date Handling Recommendation


    Although Open Clinical History can accept the configured display format, integrations should always send:

    `YYYY-MM-DDDD`

    For example:

    `"dob": "1971-09-05"`

    rather than:

    `"dob": "05/09/1971"`

    This makes the payload independent of the user-interface date configuration and avoids ambiguity between Australian and US date formats.


    ---

    ## HTTP Status Codes


    Common HTTP responses include:
    | HTTP HTTP| Meaning |
    | --- | 200--- |
    | `200` | Request completed successfully
    |
    | 202`202` | Import accepted for asynchronous processing
    |
    | 400`400` | Invalid request or field
    |
    | 401`401` | Missing, invalid, expired or revoked token
    |
    | 403`403` | Token does not have the required scope
    |
    | 404`404` | Document, job or queue item does not exist
    |
    | 405`405` | Incorrect HTTP method
    |
    | 409`409` | Current state does not permit the requested operation
    |
    | 413`413` | Document exceeds the configured size limit
    |
    | 422`422` | Document/patient could not be stored or linked
    |
    | 500`500` | Internal application error
    |
    | 503`503` | API, queue or worker unavailable/configuration incomplete
    |
    ---


    ## Common API Error Codes


    ### Authentication
    missing_token
    invalid_token
    `missing_token`
    insufficient_scope
    `invalid_token`


    `insufficient_scope`

    ### Configuration
    not_configured
    api_disabled
    `not_configured`
    https_required
    queue_disabled
    `api_disabled`

    `https_required`

    `queue_disabled`

    ### Request format
    bad_json
    method_not_allowed
    `bad_json`
    unknown_action
    missing_target
    `method_not_allowed`

    `unknown_action`

    `missing_target`

    ### Document import
    missing_text
    empty_document
    `missing_text`
    too_large
    upload_failed
    `empty_document`
    missing_record_number
    bad_record_number
    `too_large`
    bad_dob
    store_failed
    `upload_failed`
    link_failed
    worker_unavailable
    `missing_record_number`

    `bad_record_number`

    `bad_dob`

    `store_failed`

    `link_failed`

    `patient_uuid_missing`

    `worker_unavailable`

    ### Processing
    still_extracting
    bad_job
    `still_extracting`
    not_found
    `bad_job`


    `not_found`

    ### Queue management
    unknown_op
    not_applicable
    `unknown_op`


    `not_applicable`


    ---

    ## Example Error


    An invalid date may return:

    `HTTP 400400`

    ```json
    {
    "ok": false,
    "error": {
    "code": "bad_dob",
    "message": "dob must be ISO YYYY-MM-DD or the configured display format (dd/mm/yyyy)."
    }
    }
    ```


    Applications should use the machine-readable:readable
    `error.codecode` 

    for program logic and treat:

    treat
    `error.messagemessage` 

    as diagnostic information.



    ---

    ## Queue Worker Requirement


    When the ingest queue is enabled:

    `queue_enabled = truetrue`

    submitting a document does not**not** directly perform extraction.


    It creates a queue item.


    At least one instance of:

    `queue_worker.phpphp`

    must therefore be running.


    If no worker is running, submissions can still return:return
    `202 AcceptedAccepted` 

    but will remain in:

    queued
    `queued`


    until a worker becomes available.


    Queue health should therefore be monitored as part of production operations.


    ---

    ## Behaviour Without the Queue


    If the ingest_queue`ingest_queue` table is unavailable or:

    `queue_enabled = falsefalse`

    the API falls back to its earlier behaviour and spawns an extraction worker for each import.


    The response still returns:returns
    `202 AcceptedAccepted` 

    but contains a job identifier rather than queue information.


    The response still includes `patient_id`, `patient_uuid` and `patient_url`, so the public patient reference is available immediately in this mode as well.

    The suggested poll interval becomes:

    `5 secondsseconds`

    This mode is unbounded and is less appropriate for burst or high-volume integrations.


    The controlled ingest queue is recommended.


    ---

    ## API Security Model


    API permissions are capability based.


    A token with:
    status
    `status`


    scope can currently query API document and queue status generally.


    A token with:
    commit
    `commit`


    scope can request a commit for a supplied document identifier.


    The current API does not restrict documents to the token that originally submitted them.


    Therefore:

    > API tokens should currently be treated as trusted integration credentials for the Open Clinical History installation, rather than as isolated per-patient or per-tenant credentials.

    Use least-privilege scopes and issue separate tokens for different integrations where appropriate.


    ---

    ## Suggested Integration Requirements


    A production integration should:

    • -

      use HTTPS

    -

    store API tokens securely

    -

    use ISO dates

    -

    use a stable patient record_number

    `record_number`
    -

    provide a meaningful source_name

    `source_name`
    -

    provide a globally unique external_ref

    `external_ref`
    -

    store the returned document_id

    `patient_uuid` as the

    durable Open Clinical History patient reference

    - store the returned `document_id` for document-level monitoring and reconciliation
    - treat `patient_id` as an internal numeric identifier rather than the preferred external reference
    - retain `status_url` or reconstruct it from `document_id`
    - respect poll_after`poll_after`
    -

    poll status rather than repeatedly submitting

    - use

    the same `external_ref` when retrying an uncertain submission

    - handle asynchronous processing
    -

    handle failed imports explicitly

    -

    use API error codes rather than parsing error messages

    -

    avoid assuming every extracted event will be automatically committed

    -

    monitor ingest-queue health


    ---


    ## Minimum JSON Request


    The smallest valid JSON import is:

    ```json
    {
    "record_number": "MRN-1001",
    "text": "Clinical history text"
    }
    ```


    All other fields have defaults or are optional.


    For a real integration, however, the recommended payload is:

    ```json
    {
    "record_number": "MRN-1001",
    "display_name": "Example Patient",
    "dob": "1967-04-02",
    "sex": "female",
    "source_name": "specialist-letter-84721.txt",
    "text": "Clinical history text...",
    "auto_commit": true,
    "external_ref": "source-system:MRN-1001:84721"
    }
    ```


    ---

    ## Recommended Production Sequence

    ```text
    Create API token
    |
    v
    GET ping
    |
    v
    POST import
    |
    v
    Store patient_uuid
    Store document_id
    Store external_ref
    Store status_url
    |
    v
    Poll status
    |
    +---- queued/extracting ---> continue polling
    |
    +---- committed -----------> finished
    |
    +---- extracted -----------> commit if required
    |
    +---- failed --------------> investigate/retry
    ```


    The Open Clinical History patient reference is available before asynchronous clinical processing finishes. There is therefore no need for a source system to wait until `committed` before recording the relationship between its own patient and the Open Clinical History patient.

    ---

    ## Summary


    `history_api_import.phpphp` provides the machine-to-machine entry point into the Open Clinical History patient-processing pipeline.


    The API deliberately separates:
    Submission
    ```text
    Patient find/create
    |
    +---- return patient_uuid immediately
    |
    v
    Document submission
    |
    v
    Asynchronous extraction
    |
    v
    Clinical audit
    |
    v
    Safe commit
    ```


    This means external systems can provide **raw, unstructured clinical recordsrecords** while Open Clinical History performs the extraction, SNOMED classification, anatomical mapping and clinical-safety checks required to turn those records into a longitudinal patient history.


    The external system does not need to pre-classify the clinical content before submission.

    It also does not need to wait for processing to finish before receiving the Open Clinical History patient identifier. The initial successful import response provides:

    - `patient_uuid` — the stable public patient identifier
    - `patient_url` — a convenience link to the patient viewer
    - `document_id` — the specific imported source document
    - `external_ref` — the source-system idempotency reference
    - `status_url` — the endpoint used to monitor asynchronous processing

    For integrations, `patient_uuid` should be stored as the durable Open Clinical History patient reference, while `document_id` should be stored for tracking the lifecycle of each imported document.