> For the complete documentation index, see [llms.txt](https://docs.bloodgpt.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.bloodgpt.com/submitting-tests/process.md).

# Processing API

This document outlines the API for submitting blood test reports for analysis and retrieving the processed results.

The core endpoint for submitting blood tests for analysis and retrieving the processed report. Submit a PDF/HL7 file or structured JSON, then poll the pre-signed `download_url` (or receive a [webhook](/interpretation-and-results/webhooks.md)).

{% hint style="info" %}
Please report any bugs, issues, or feature requests to <contact@bloodgpt.com>.
{% endhint %}

## API Flow Diagram

```mermaid
sequenceDiagram
    participant Client
    participant BloodGPT API
    participant BloodGPT Service
    participant BloodGPT CDN

    Client->>BloodGPT API: POST /api/v1/process (file or JSON)
    activate BloodGPT API
    BloodGPT API-->>Client: 202 Accepted (download_url)
    deactivate BloodGPT API

    Note right of BloodGPT API: Processing starts...

    BloodGPT Service->>BloodGPT CDN: Uploads generated report
    
    loop Polling CDN
        Client->>BloodGPT CDN: GET download_url
        alt Report not ready
            BloodGPT CDN-->>Client: 404 Not Found
        else Report is ready
            BloodGPT CDN-->>Client: 200 OK (File Content)
        end
    end
```

## Base URLs

* **Production:** Coming soon
* **Sandbox:** `https://sandbox.bloodgpt.com`

## Authentication & Security

Include your API key in all requests in the Authorization header.

{% hint style="info" %}
Use the provided API key in the Authorization header:

```
Authorization: Bearer YOUR_API_KEY
```

{% endhint %}

To obtain an API key or request access, please contact us at <contact@bloodgpt.com>.

A few properties worth knowing when you build against this endpoint:

* **TLS only.** All requests must use HTTPS. Send the key in the `Authorization` header, never in a query string, where it would land in proxy and browser logs.
* **The key is your tenant.** Each key resolves to one organization, and every patient, test, storage prefix, and clinical record is scoped to it. Treat it as a credential for your entire dataset: keep it server-side, rotate it if it is exposed, and use separate keys per environment.
* **`download_url` is a time-limited pre-signed link.** It is valid for 24 hours and grants read access to the report to anyone holding it, so pass it on the way you would pass the report itself.
* **Data location.** Requests are processed in Google Cloud `europe-west4` (Netherlands); clinical records are written to a FHIR store dedicated to your organization. See [Privacy & Security](/getting-started/readme.md#privacy-and-security) for the wider picture.

***

## Submit Report for Processing

This endpoint accepts a report for asynchronous processing. In response, it provides a pre-signed URL where the final, processed report will be made available.

**Endpoint:** `POST /api/v1/process`

You can submit data in two ways. Each method is shown below.

{% stepper %}
{% step %}

#### Structured JSON Data

Submit using `Content-Type: application/json`.

Headers:

```
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
```

Request Body (JSON):

```json
{
  "patient_id": "patient11",
  "external_id": "test-payload-{{$timestamp}}", // must be unique
  "patient_info": {
    "birth_date": "1985-03-25",
    "gender": "male",
    "weight": 80, // optional
    "height": 175 // optional
  },
  "payload": {
    "test_date": "2025-10-08T12:00:00Z",
    "parameters": [
      {
        "name": "Glucosio",
        "value": 144,
        "unit": "mg/dL",
        "range": "70-110"
      },
      {
        "name": "Emoglobina",
        "value": 11.2,
        "unit": "g/dL",
        "range": "13.5-17.5"
      },
      {
        "name": "Colesterolo Totale",
        "value": 140,
        "unit": "mg/dL",
        "range": "< 200"
      },
      {
        "name": "Colesterolo HDL",
        "value": 43,
        "unit": "mg/dL",
        "range": "> 40"
      },
      {
        "name": "Trigliceridi",
        "value": 121,
        "unit": "mg/dL",
        "range": "< 150"
      }
    ]
  },
  "processing_options": {
    "target_language": "en",
    "use_panel_grouping": true
  }
}
```

{% hint style="info" %}
To be notified when processing finishes, configure a webhook for your organization (see [Webhooks](/interpretation-and-results/webhooks.md)). The `/api/v1/process` request body does not accept a per-request `webhook_url` — that override is only available on [Batch Processing](/submitting-tests/batch.md).
{% endhint %}
{% endstep %}

{% step %}

#### File Upload

{% hint style="warning" %}
PDF recognition is available upon request, following the approval of the format and recognition quality by BloodGPT.
{% endhint %}

Submit using `Content-Type: multipart/form-data`.

Headers:

```
Content-Type: multipart/form-data
Authorization: Bearer YOUR_API_KEY
```

Request Body (multipart/form-data):

* **file:** attach a PDF file with blood test results
* **data:** json data with request and patient info, check example below

```json
{
   "patient_id": "123",
   "external_id": "123", // must be unique for every request
   "patient_info": { 
      "birth_date": "1985-03-25", // YYYY-MM-DD
      "gender": "male",
      "weight": 80, // optional
      "height": 175 // optional
   },
   "processing_options": {"target_language": "en", "use_panel_grouping": true}
}
```

Request example:

```bash
curl --request POST \
 --url https://sandbox.bloodgpt.com/api/v1/process \
 --header 'authorization: Bearer YOUR_API_KEY' \
 --header 'content-type: multipart/form-data' \
 --form file=@/path/to/file \
 --form 'data={
"patient_id": "123",
"external_id": "123",
"patient_info": {
  "birth_date": "1985-03-25",
  "gender": "male",
  "weight": 80,
  "height": 175
},
"processing_options": {"target_language": "en", "use_panel_grouping": true}
}'
```

{% endstep %}
{% endstepper %}

#### JSON Data Structure

Root Level Fields:

* patient\_id (string, required): A unique identifier for the patient, used for tracking trends across visits.
* external\_id (string, required): A unique identifier for the specific case or visit. Must be unique per request.
* patient\_info (object, optional): Patient demographic information. All sub-fields are optional.
  * birth\_date (string, optional): Patient's date of birth (YYYY-MM-DD).
  * gender (string, optional): "male" | "female" | "other" | "unknown".
  * weight (number, optional): Weight in kilograms.
  * height (number, optional): Height in centimeters.

Payload Object (required when submitting structured JSON instead of a file):

* test\_date (string, optional): ISO 8601 timestamp (YYYY-MM-DDTHH:flag\_mm:ssZ) of when the blood test was performed.
* parameters (array, required): Array of test parameters (at least one).
  * name (string, required): The original name of the parameter.
  * value (number | string | null, required): The measured value. Use a number for quantitative results, a string for qualitative results (e.g. `"Negative"`, `"Clear"`), or `null` for an explicitly missing measurement.
  * unit (string, required): The unit of measurement. An empty string is allowed for unitless qualitative observations.
  * range (string, optional): The reference range (e.g., "min-max").
  * loinc (string, optional): The LOINC identifier for standardization.

Processing Options (optional):

* target\_language (string, optional): ISO 639-1 language code (e.g., "en", "ru", "it"). Default: "en".
* use\_panel\_grouping (boolean, optional): Group results by panel in the report.
* selected\_tests (array of string, optional): Restrict the report to specific tests.

### API Response (`202 Accepted`)

Upon successful submission, the API immediately returns a pre-signed URL. This URL is where the final report will be uploaded once processing is complete.

Response Body:

```json
{
  "request_id": "req_abc123",
  "status": "processing",
  "test_id": "bg_20250724_abc123def456",
  "external_id": "2025-0909-001",
  "download_url": "https://cdn.bloodgpt.com/results/report.pdf?sig=abc123xyz&expires=1663286400"
}
```

***

## Download the Processed Report

To retrieve the report, poll the `download_url` received in the previous step.

{% hint style="info" %}
**Want the data instead of the PDF?** The `download_url` serves the rendered PDF and there is no request option that changes that. To get the same analysis as structured JSON — parameters, overview, follow-ups, trends — keep the `test_id` from the `202` response and call [`GET /api/v1/download/{test_id}`](/interpretation-and-results/download.md) once processing is complete.
{% endhint %}

{% stepper %}
{% step %}

#### Initial wait

After receiving the `download_url`, wait for an initial period (e.g., 30 seconds).
{% endstep %}

{% step %}

#### Poll the download URL

Send a `GET` request to the `download_url`.
{% endstep %}

{% step %}

#### Handle 404

If the CDN responds with `404 Not Found`, the file is not yet ready. Wait for a short interval (e.g., 30 seconds) and try again.
{% endstep %}

{% step %}

#### Handle 200

If the CDN responds with `200 OK`, the response body will contain the processed report file.
{% endstep %}

{% step %}

#### Timeout

Implement a client-side timeout (e.g., after 5 minutes). If the file is not available after the timeout, consider the processing to have failed and re-submit if necessary.
{% endstep %}
{% endstepper %}

### Report example

{% file src="/files/vOD2uHn131yITJPKlvJt" %}

### Responses from CDN

* `200 OK`: The request was successful, and the file is returned in the response body.
* `404 Not Found`: The file is not yet available at the URL.
* `403 Forbidden`: The pre-signed URL has expired. You will need to re-submit the initial request to get a new URL.

***

## Error Handling

These are general API errors for the initial `POST` request. Errors related to the download are handled by the CDN polling mechanism.

See [Errors & Validation](/reference/errors.md) for the full error-code list, request-validation rules, and the date formats accepted in `birth_date` / `test_date`.

<details>

<summary>401 Unauthorized</summary>

```json
{
  "error": "invalid_api_key",
  "message": "The provided API key is invalid or expired"
}
```

</details>

<details>

<summary>413 Payload Too Large</summary>

```json
{
  "error": "file_too_large",
  "message": "File size exceeds 40MB limit",
  "max_size_mb": 40
}
```

</details>

<details>

<summary>422 Unprocessable Entity</summary>

```json
{
  "error": "invalid_request_body",
  "message": "The provided JSON or form data is invalid."
}
```

</details>

***

## Additional Features

{% hint style="info" %}
**Alternative to polling**: Instead of polling the CDN, you can receive instant notifications via [Webhooks](/interpretation-and-results/webhooks.md).
{% endhint %}

* [**Webhooks**](/interpretation-and-results/webhooks.md) — Receive real-time notifications when processing completes
* [**HL7 Format**](/submitting-tests/hl7.md) — Submit blood tests using HL7 v2.x laboratory messages
* [**Batch Processing**](/submitting-tests/batch.md) — Submit up to 100 tests in a single request
* [**Upload API**](/submitting-tests/upload.md) — Submit documents and extract structured data (JSON/FHIR)
* [**Interpret & Summary**](/interpretation-and-results/interpret.md) — Trigger AI interpretation for a test or a whole patient
* [**Patients API**](/patients-and-catalog/patients.md) — Manage patients and link them to results
* [**Product Catalog API**](/patients-and-catalog/product-catalog.md) — Power product recommendations from results
* [**Widgets**](/reference/widgets.md) — Embed report views with scoped browser tokens
* [**Errors & Validation**](/reference/errors.md) — Error codes, validation rules, and accepted date formats
