Aller au contenu principal

Implementation

Here, you will find the basic documentation pertaining the iframe integration. This includes specifications about the parameters of the URL, details about the output and the input, and the communication methods so you can store this data.

Customize your iframe

You can activate or deactivate features of the iframe and customise its appearance by appending parameters to the base URL. Below is a detailed explanation of each parameter and a link to our iframe generator for visual previews and URL generation.

Iframe generator

Utilize our interactive iframe generator below to customize and preview your iframe. Adjust the settings as needed, and use the generated URL for embedding.

#
#

Identifier replacement

Ensure to replace the default company identifier with your specific company identifier to ensure functionality in a production environment.

Parameters description

The iframe URL supports several parameters to tailor its functionality and design:

  • company: Your unique company identifier provided by Legit.Health.
  • companyCallbackUrl: Overrides the default callback URL to which the diagnostic report will be sent.
  • extraData: A string that includes additional data for the report, such as patient identifiers, which remain hidden from users.
  • primary and secondary: Hexadecimal color codes to customize the interface colors.
  • fontFamily: Specifies the font family used within the iframe. Supported options: Roboto, Montserrat.
  • enableResult: Toggles the display of diagnostic results to the user.
  • isForPatient: Simplifies the text in the questionnaires so that inexperienced users can understand.
  • locale: Sets the application's language. Available options: en (English), es (Spanish).
  • showQuestionnairesHeader: Controls the visibility of the questionnaire title, defaulting to visible (1).
  • enableAnamnesis: Shows or hides the anamnesis form, hidden by default (0).
  • You can also adjust camera access settings:
  • enableAlternativeCameraModule: Activates the alternative camera module, which is off by default (0).
  • enableAlternativeCameraModuleAndroid: Activates the alternative camera module on Android devices, also off by default (0).
  • enableExtendedInstructions: Activates the extended instructions for taking photos, which are off by default (0).
  • macroscopicMedia: Sets the requirement level for uploading a context or macroscopic image, disabled by default (disabled).

How to obtain a company identifier

To generate a company-specific application key with an expiration date, access the following endpoint from our Deep Link API:

Generate here: https://deep-link-spec.legit.health

Detailed description of parameters

Patient use

When the primary users of the iframe are patients, you can utilize the isForPatient=1 parameter within the URL. Activating this option will modify the text within the questionnaires to make them more understandable. This may be a good idea because some clinical questionnaire use language that is too technical.

For instance, instead of displaying the basic label Itchiness for the itchiness question, the text will be transformed into a more straightforward query: How would you describe how itchy you feel?. This enhancement aims to improve the questionnaire's accessibility and ease of completion for patients.

Visibility of the results

The results can be shown or hidden to users depending on the param enableResult. The default behaviour is showing the results to the user after he or she has uploaded the photo, but you can pass enableResult=0 to show only a message.

Colors and font family

Primary and secondary colours can be customized using the params primary and secondary in the URL.

You can also select which font family you want to use. Currently, we support Roboto and Montserrat. Feel free to request aditional fonts and we will do our best to include them in the iframe.

Language

The language of the iframe can be customized using the param locale and providing the values es or en. If you require another language, please reach out to us and we will arrange it.

Extra data

Given that you have available the extraData field that accepts any string, you can pass any additional value you need to be attached to the report. For example, if you need to pass patient and insurance provider information, you would do it as follows:

const data = toBase64(
JSON.stringify({ patient: "XXX", insuranceProvider: "YYY" })
);

Anamnesis questionnaire

The parameter enableAnamnesis allows to show or hide the anamnesis form. The default value is 0 so you need to pass enableAnamnesis=1 to show the anamnesis form.

Questionnaires

The parameter showQuestionnaireTitle allows to show or hide the title above the questionnaires. You can identify these questionnaire under the title "Medical questionnaire".

Enable alternative camera module

Frameworks like Ionic play a crucial role in developing cross-platform mobile and web applications. However, they often limit hardware access, particularly to the camera, when using iframe. This restriction is notably problematic in applications that require immediate camera access, as Ionic and similar frameworks typically block camera usage through a file type input element within an iframe.

info

Our findings indicate that this issue mainly affects Android devices running Ionic-developed applications displaying our solution in an iframe. You may need to enable the enableAlternativeCameraModuleAndroid parameter specifically for these devices.

To overcome these restrictions, we have developed a bespoke module that allows camera access directly within the iframe. Activate this feature by appending the following parameters to your URL:

  • enableAlternativeCameraModule=1: This parameter enables this alternative module within the iframe. It is set to off by default (enableAlternativeCameraModule=0), as a standard "file" type input is adequate for most use cases.
  • enableAlternativeCameraModuleAndroid=1: This parameter is specifically designed for Android devices, activating the alternative camera module where it is otherwise disabled by default (enableAlternativeCameraModuleAndroid=0).

Implementing these parameters ensures our solution is adaptable and meets the diverse needs of different devices and operating systems, effectively bypassing the typical iframe limitations in platforms like Ionic.

Enable extended instructions

By default, the iFrame shows minimal instructions for guiding users through the photo-taking process. To provide more comprehensive guidance, enable the parameter enableExtendedInstructions=1. This will activate a complete set of instructions, including best practices for taking photos.

Macroscopic or context image

The macroscopicMedia parameter controls if users need to upload images as part of the diagnostic process. It has three possible settings:

  • disabled: No image upload required and the field is not shown. This is the default value.
  • optional: Users can upload a macroscopic image, but it’s not mandatory.
  • required: Users must upload a macroscopic to proceed.

This parameter helps tailor the workflow depending on whether visual data is essential, optional, or not needed. It’s particularly useful for adjusting the user experience in medical assessments where visual context may be valuable or critical.

Input

In most cases, users interacting with the iframe are required to submit only two pieces of information: the specific body area affected and the images of the lesion. Optionally, an anamnesis questionnaire can be included, to gather more detailed patient history.

If our artificial intelligence identifies a condition with high probability, the iframe will automatically display a clinical questionnaire related to that condition. For instance, if there is a 98% probability that a lesion is psoriasis, the patient will be prompted to complete the PASI questionnaire. Completing this questionnaire provides additional details necessary for assessing the severity of the condition.

Anamnesis

Generic questions

The anamnesis questionnaire is a set of questions that are asked to the patient to gather information about the patient's medical history:

  1. What is the reason for the consultation? How did the problem start? Describe the origin.
  2. Do you have any allergies, especially to medications? If yes, list allergies.
  3. Are you taking any medication or treatment? If yes, explain what treatment you are taking.
  4. Do you have any major illness? Have you had anything operated on?
  5. Is there a history of any major illness in your family?

Condition-specific questions

The condition-specific questions can be found in the section Advanced > Scoring systems. Indeed, the questions that are specific of a condition are actually those that each scoring system considers.

Images

In the iframe, the user uploads the images of their lesion. They achieve this by clicking on a text that reads Click here to add an image. This allows them to open the camera from their device, or to upload an image from their files.

Multiple images

It is worth mentioning that users can upload multiple images of the same lestion, like this:

All images belong to the same report

When this happens, all images will belong to the same DiagnosticReport and constitute a single call to the device.

Image quality

As you may know, the device has a technology called DIQA that measures the visual quality of the image. In the iframe, if the image quality is below 60%, the iframe will ask users to re-upload the image by making some corrections.

The value of image quality can be found in the message posted by the iframe.

"originalMedia": {
"type": "Image",
"modality": "Clinical",
"diqaScore": 74, // 👈
// ...
},
Further reading

Please read the Output section of the Instructions For Use for more info.

Output (callbacks)

For a complete understanding of the output of our artificial intelligence, you can visit the sections of our documentation where we explain the values we compute depending on the use case: diagnostic support or severity measurement.

Further reading

Please read the Output section of the Instructions For Use for more info.

Depending whether or not you add the key knownConditionForThisImage, the output from the device will be different, following this logic:

Callback URL

Upon the successful processing of each uploaded image, our server dispatches a JSON payload containing detailed information about the diagnostic process to the configured endpoint. This JSON payload encapsulates multiple layers of diagnostic data and metrics. Here's an example of what you can expect:

{
"id": "0189bafa-0610-7349-a0ab-eb53695b27fd",
"url": "https://iframe.legit.health?companyId=XXXX&diagnosticReportId=signedId",
"pdf": "https://back-{pre}.legit.health/s2s-api/v2/anonymous-diagnostic-reports/encryptedId?format=pdf",
"extraData": "some extra data",
"visitIdentifier": "visit identifier",
"anamnesisGeneral": [
{
"question": "What is the reason for the consultation? How did the problem start? Describe the origin.",
"answer": "I have acne on my face and neck. It started 2 months ago."
},
{
"question": "Do you have any allergies, especially to medications? If yes, list allergies."
"answer": "No"
},
{
"question": "Are you taking any medication or treatment? If yes, explain what treatment you are taking.",
"answer": "No"
},{

"question": "Do you have any major illness? Have you had anything operated on?",
"answer": "No"
},
{
"question": "Is there a history of any major illness in your family?",
"answer": "No"
}
],
"pathology": {
"name": "Acne",
"code": "Acne",
"icd11": "ED80"
},
"bodySite": {
"code": "HEAD_FRONT",
"name": "Face and neck"
},
"createdAt": "2023-08-03T12:38:10+02:00",
"result": {
"id": "0189bafa-0610-7349-a0ab-eb536a2185f6",
"metrics": {
"sensitivity": 83.31,
"specificity": 99.53,
"entropy": null
},
"preliminaryFindings": {
"hasCondition": 100,
"malignancy": 0.02,
"adjustedMalignancy": null,
"pigmentedLesion": 0,
"urgentReferral": 100,
"highPriorityReferral": 100
},
"iaSeconds": 0.88,
"observations": [
{
"media": {
"type": "Image",
"modality": "Clinical",
"diqaScore": 86,
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
}
}
],
"conclusions": [
{
"probability": 99.65,
"pathology": {
"name": "Acne",
"code": "Acne",
"icd11": "ED80"
}
},
{
"probability": 0.06,
"pathology": {
"name": "Varicella",
"code": "Varicella",
"icd11": "1E90"
}
}
],
"scoringSystems": [
{
"scoringSystem": {
"name": "Acne lesion estimation grading index",
"code": "ALEGI"
},
"score": {
"value": 8,
"severity": {
"value": 1,
"categories": [
{
"code": "None",
"category": "None",
"min": 0.0,
"max": 0.0,
"severity": 1,
"severityAsString": "low"
},
{
"code": "Grade 1",
"category": "Grade 1",
"min": 0.0,
"max": 10.0,
"severity": 1,
"severityAsString": "low"
},
{
"code": "Grade 2",
"category": "Grade 2",
"min": 10.0,
"max": 20.0,
"severity": 1,
"severityAsString": "low"
}
],
}
},
"facets": [
{
"facet": {
"name": "Acne lesion density",
"description": ""
},
"value": {
"text": "None (0)",
"raw": 0
}
},
{
"facet": {
"name": "Number of lesions",
"description": ""
},
"value": {
"text": "Mild (0-10)",
"raw": 5
}
}
]
}
]
}
}

Fundamental fields

  • id: A unique identifier of the Diagnostic Report.
  • url: link to visualize the diagnostic report inside Legit.Health's interface.
  • extraData: A string value passed as a query parameter when you loaded the iframe.
  • createdAt: Timestamp indicating the time of creation for this diagnostic report.

Body site and pathology

These fields contain information about the location (bodySite) and type (pathology) of the lesion captured in the image. These fields are populated when the likelihood of the most probable conclusion surpasses a predetermined threshold or if it was set during image submission.

"bodySite": {
"code": "HEAD_FRONT",
"name": "Face and neck"
},
"pathology": {
"name": "Acne",
"code": "Acne",
"icde11": "E90"
}

Anamnesis general

The anamnesisGeneral field is an array of questions and their corresponding answers. These questions are asked to the patient before the image is submitted for analysis and their objetive is to gather information about the patient's medical history:

"anamnesisGeneral": [
{
"question": "What is the reason for the consultation? How did the problem start? Describe the origin.",
"answer": "I have acne on my face and neck. It started 2 months ago."
},
...
]

Diagnostic result

The result field encapsulates the comprehensive results of the diagnostic process. This includes:

  • metrics: Measures of sensitivity and specificity of the diagnostic process.
"metrics": {
"sensitivity": 83.31,
"specificity": 99.53,
"entropy": null
}
  • preliminaryFindings: An array of preliminary suspicions and their likelihoods, including the probability of malignancy and the requirement for a biopsy.
"preliminaryFindings": {
"hasCondition": 100,
"malignancy": 0.02,
"adjustedMalignancy": null,
"pigmentedLesion": 0,
"urgentReferral": 100,
"highPriorityReferral": 100
}
  • iaSeconds: The processing time consumed by the AI for image analysis.

Observations

The observations field is an array of medias, each containing one field: media.

media: the media sent to the algorithm to be analyzed. It includes:

  • type: The format of the image.
  • modality: The context or manner of the image capture.
  • diqaScore: The Dermatology Image Quality Assessment (DIQA) score.
  • url: The direct S3 URL to the image. It is a time-limited signed URL; download it within 30 minutes of generation if you plan to use it later.
"observations": [
{
"media": {
"type": "Image",
"modality": "Clinical",
"diqaScore": 86,
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
}
},
...
]

Diagnostic conclusions

The conclusions field is an array of diagnostic outcomes, each containing a pathology type and its associated probability.

"conclusions": [
{
"probability": 99.65,
"pathology": {
"name": "Acne",
"code": "Acne",
"icd11": "ED80"
}
},
...
],

Scoring systems

The scoringSystems field houses one or more scoring models for the identified pathology. These models come into play when the likelihood of the most probable conclusion surpasses a certain threshold. They are used to estimate the severity of the most probable condition:

    "scoringSystems": [
{
"scoringSystem": {
"name": "Acne lesion estimation grading index",
"code": "ALEGI"
},
"score": {
"value": 8,
"severity": {
"value": 1,
"categories": [
{
"code": "None",
"category": "None",
"min": 0.0,
"max": 0.0,
"severity": 1,
"severityAsString": "low"
},
...
],
}
},
"facets": [
{
"facet": {
"name": "Acne lesion density",
"description": ""
},
"value": {
"text": "None (0)",
"raw": 0
}
},
...
]
}
]

In each scoring system:

  • scoringSystem includes the name and code of the scoring methodology.
  • score.value indicates the calculated score according to the system's rules.
  • score.severity signifies the severity associated with the computed score. It has three possible values:
    • 1: low
    • 2: moderate
    • 3: high
  • facets is an array of facets used in score calculation, with each object containing the facet's information and the corresponding raw and displayed values.

Comprehensive scoring systems information

For an exhaustive understanding of scoring systems, their identifiers, and facets, you can download the detailed documentation:

Callback URL (FHIR)

Upon the successful processing of each uploaded image, our server can dispathc a FHIR-compliant JSON payload containing detailed information about the diagnostic process to the configured endpoint. This payload follows the FHIR (Fast Healthcare Interoperability Resources) standard, ensuring structured and interoperable healthcare data. It encapsulates multiple layers of diagnostic data, including patient history, clinical observations, AI-based analysis, and scoring systems. Here's an example of what you can expect:

{
"success": true,
"message": "Diagnostic report <0194b1e2-d73e-71ba-9360-7b7a0ffa551b>",
"data": {
"resourceType": "DiagnosticReport",
"id": "cUVHY3lUcEFSdzdhVklrMXFKS2k5ZzBxaHBYWXNERTNFWjBqR0Rwd3AxeXkrc2hqNDNCdC8rM1NtUVhobEk0eQ==",
"presentedForm": {
"practitioner": {
"url": "https://iframe.legit.health?companyId=XXXX&diagnosticReportId=signedId",
"pdf": "https://back-{pre}.legit.health/s2s-api/v2/anonymous-diagnostic-reports/encryptedId?format=pdf",
},
"patient": {
"url": null,
"pdf": null
}
},
"encounter": {
"identifier": {
"value": "XYZ"
}
},
"issued": "2025-01-29T12:46:57+01:00",
"condition": {
"pathology": null,
"bodySite": {
"coding": [
{
"system": "https:\/\/legit.health\/integration\/json-only\/codes\/body-sites",
"code": "TRUNK_FRONT",
"display": "Chest and belly"
}
]
}
},
"extraData": "some payload",
"anamnesisGeneral": {
"resourceType": "QuestionnaireResponse",
"status": "completed",
"item": [
{
"linkId": 1,
"text": "Question one",
"answer": [
{
"valueString": "answer"
}
]
}
]
},
"macroscopicMedia": {
"resourceType": "Media",
"content": {
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
},
"type": "Image",
"annotations": null
},
"result": {
"id": "0194b1e2-d740-70d6-9435-a3606f0fa491",
"performanceIndicators": {
"resourceType": "Observation",
"component": [
{
"code": {
"coding": [
{
"code": "sensitivity",
"display": "Sensitivity"
}
]
},
"valueQuantity": {
"value": 91.16,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "specificity",
"display": "Specificity"
}
]
},
"valueQuantity": {
"value": 96.58,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "entropy",
"display": "Entropy"
}
]
},
"valueQuantity": {
"value": null,
"unit": "%"
}
}
]
},
"clinicalIndicators": {
"resourceType": "Observation",
"component": [
{
"code": {
"coding": [
{
"code": "hasCondition",
"display": "Has condition"
}
]
},
"valueQuantity": {
"value": 100.0,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "malignancy",
"display": "Malignancy"
}
]
},
"valueQuantity": {
"value": 0.0,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "adjustedMalignancy",
"display": "Adjusted malignancy"
}
]
},
"valueQuantity": {
"value": null,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "pigmentedLesion",
"display": "Pigmented lesion"
}
]
},
"valueQuantity": {
"value": null,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "urgentReferral",
"display": "Urgent referral"
}
]
},
"valueQuantity": {
"value": null,
"unit": "%"
}
},
{
"code": {
"coding": [
{
"code": "highPriorityReferral",
"display": "High priority referral"
}
]
},
"valueQuantity": {
"value": null,
"unit": "%"
}
}
]
},
"analysisDuration": 0.91670727729797,
"observations": [
{
"media": {
"resourceType": "Media",
"content": {
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
},
"type": "Image",
"modality": "Clinical",
"diqaScore": 86.0
}
},
{
"media": {
"resourceType": "Media",
"content": {
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
},
"type": "Image",
"modality": "Clinical",
"diqaScore": 92.0
}
}
],
"conclusions": [
{
"pathology": {
"coding": [
{
"code": "Psoriasis",
"display": "Psoriasis",
"system": "https:\/\/legit.health\/integration\/json-only\/codes\/conditions"
},
{
"code": "EA90",
"display": "Psoriasis",
"system": "ICD11"
}
]
},
"probability": 80.0
},
{
"pathology": {
"coding": [
{
"code": "Palmoplantar psoriasis",
"display": "Palmoplantar psoriasis",
"system": "https:\/\/legit.health\/integration\/json-only\/codes\/conditions"
},
{
"code": "EA90.42, EA90.5Y",
"display": "Palmoplantar psoriasis",
"system": "ICD11"
}
]
},
"probability": 20.0
}
],
"questionnaires": [
{
"resourceType": "QuestionnaireResponse",
"questionnaire": "APASI_LOCAL",
"scoringSystem": {
"text": "Local automatic psoriasis area and severity index",
"coding": [
{
"code": "APASI_LOCAL",
"display": "Local automatic psoriasis area and severity index"
}
]
},
"score": {
"valueQuantity": {
"value": 6.3
},
"system": "http:\/\/unitsofmeasure.org",
"unit": "points",
"interpretation": {
"coding": [
{
"code": 3,
"display": "Severe"
}
],
"text": "Severe"
},
"interpretationCategories": [
{
"min": 0.0,
"max": 0.0,
"text": "None",
"coding": [
{
"code": 1,
"display": "low"
}
]
},
{
"min": 0.0,
"max": 2.1,
"text": "Mild",
"coding": [
{
"code": 1,
"display": "low"
}
]
},
{
"min": 2.1,
"max": 4.5,
"text": "Moderate",
"coding": [
{
"code": 2,
"display": "moderate"
}
]
},
{
"min": 4.5,
"max": 21.6,
"text": "Severe",
"coding": [
{
"code": 3,
"display": "high"
}
]
}
]
},
"explainabilityMedia": {
"resourceType": "Media",
"content": {
"url": "https://legit-dev.s3.eu-west-3.amazonaws.com/diagnostic-report-medias/bbb.png"
},
"annotations": [],
"type": "Image"
},
"item": [
{
"linkId": 30,
"text": "This value corresponds to the shedding intensity of the outermost layer of skin of the affected zone",
"code": [
{
"code": "Scaling",
"display": "Scaling"
}
],
"answer": [
{
"valueCoding": {
"code": 2.0,
"display": "Moderate (2)"
}
}
]
},
{
"linkId": 27,
"text": "This value corresponds to the redness intensity of the lesion",
"code": [
{
"code": "Erythema",
"display": "Erythema"
}
],
"answer": [
{
"valueCoding": {
"code": 3.0,
"display": "Severe (3)"
}
}
]
},
{
"linkId": 29,
"text": "This value corresponds to the hardening intensity of the lesion",
"code": [
{
"code": "Induration",
"display": "Induration"
}
],
"answer": [
{
"valueCoding": {
"code": 2.0,
"display": "Moderate (2)"
}
}
]
},
{
"linkId": 52,
"text": "This value corresponds to the percentage of involvement of the specific area of the body you are reporting",
"code": [
{
"code": "Affected area",
"display": "Affected area"
}
],
"answer": [
{
"valueCoding": {
"code": 3.0,
"display": "50% (3)"
}
}
]
}
]
}
]
}
},
"errorCode": null
}

Top-level fields

The response begins with three fundamental fields that indicate the request’s outcome and location of data:

{
"success": true,
"message": "Diagnostic report <id>",
"data": { ... },
"errorCode": null
}
  • success: Indicates whether the request was successfully processed.
  • message: A short text message describing the action performed. Often includes the diagnostic report’s internal identifier.
  • data: Holds the main content of the diagnostic report in FHIR format.
  • errorCode: Used to return an error code if something fails.

data object

Within "data", the diagnostic report follows the FHIR specification. It encloses all information about the patient’s encounter, clinical findings, images, and conclusions.

"data": {
"resourceType": "DiagnosticReport",
"id": "cUVHY3lUcEFSdzdhVklrMXFKS2k5ZzBxaHBYWXNERTNFWjBqR0Rwd3AxeXkrc2hqNDNCdC8rM1NtUVhobEk0eQ==",
"presentedForm": { ... },
"encounter": { ... },
"issued": "2025-01-29T12:46:57+01:00",
"condition": { ... },
"extraData": null,
"anamnesisGeneral": { ... },
"macroscopicMedia": { ... },
"result": { ... }
}

Below is a breakdown of its main sections:

  • resourceType: Specifies the FHIR resource type, which is "DiagnosticReport".
  • id: A unique, encoded identifier for this Diagnostic Report.
  • presentedForm: Contains links that allow viewing the diagnostic report in different forms (browser or PDF). Typically includes:
    • practitioner.url: A link where a practitioner can view the diagnostic report.
    • practitioner.pdf: A link to obtain a PDF of the report.
    • patient.url / patient.pdf: If relevant to the patient, these fields may provide them with URLs for viewing or downloading their report. If unused, they may remain null.
"presentedForm": {
"practitioner": {
"url": "...",
"pdf": "..."
},
"patient": {
"url": null,
"pdf": null
}
}
  • encounter: Contains the identifier that links this diagnostic report to a particular healthcare encounter or visit. The value stores the encounter’s unique string identifier.
"encounter": {
"identifier": {
"value": "some-unique-encounter-id"
}
}
  • issued: A timestamp indicating when the diagnostic report was issued or finalized.
  • condition: Describes the suspected or most likely condition and the body site:
    • pathology: May contain the name or code for a confirmed condition. Here it is null, but when present, it typically includes additional details like coding systems or official terminology.
    • bodySite: Provides standardized information about the anatomical location using code, display, and a system that references a coding standard.
"condition": {
"pathology": null,
"bodySite": {
"coding": [
{
"system": "https://legit.health/integration/json-only/codes/body-sites",
"code": "TRUNK_FRONT",
"display": "Chest and belly"
}
]
}
}
  • extraData: An optional string field for any additional that the client has passed to load the iFrame.
  • anamnesisGeneral: Stores general Q&A information about the patient’s history in FHIR’s QuestionnaireResponse format. It can contain items representing the questions and their answers.
"anamnesisGeneral": {
"resourceType": "QuestionnaireResponse",
"status": "completed",
"item": [
{
"linkId": 1,
"text": "Question one?",
"answer": [
{
"valueString": "answer"
}
]
},
...
]
}
  • resourceType: Always "QuestionnaireResponse".
  • status: Indicates the status of the questionnaire (e.g. "completed").
  • item: An array of question-answer pairs. Here it’s empty, but it could hold multiple objects each describing a question via linkId, text, and an answer.
  • macroscopicMedia: Details an image or other media of the lesion or body part. Here, it references a resource of type "Media":
    • resourceType: Always "Media".
    • content.url: A direct URL to access the uploaded file, typically a signed URL valid for a limited time.
    • type: The type of media, often "Image".
    • annotations: An optional field; it can be null or contain relevant metadata about image findings.
"macroscopicMedia": {
"resourceType": "Media",
"content": {
"url": "https://...jpg"
},
"type": "Image",
"annotations": null
}

result

The core field that captures the diagnostic process output. It includes identifiers, metrics, conclusions, and any scoring systems or questionnaires used to evaluate the condition.

"result": {
"id": "0194b1e2-d740-70d6-9435-a3606f0fa491",
"performanceIndicators": { ... },
"clinicalIndicators": { ... },
"analysisDuration": 0.916,
"observations": [ ... ],
"conclusions": [ ... ],
"questionnaires": [ ... ]
}

Below are its main subfields:

  • id Another unique identifier pertaining to the diagnostic result.
  • performanceIndicators: A FHIR Observation that shows performance metrics such as sensitivity and specificity:
    • code.coding: An array containing the codes that describe the metric name (e.g. "sensitivity", "specificity").
    • valueQuantity.value: The numeric value of the metric.
    • valueQuantity.unit: Usually "%" or other relevant units.
"performanceIndicators": {
"resourceType": "Observation",
"component": [
{
"code": {
"coding": [
{
"code": "sensitivity",
"display": "Sensitivity"
}
]
},
"valueQuantity": {
"value": 91.16,
"unit": "%"
}
},
...
]
}
  • clinicalIndicators: Another FHIR Observation describing clinical aspects such as the probability of having the condition or malignancy suspicion. Each component focuses on a specific clinical clue:
    • hasCondition: Likelihood that a condition is present.
    • malignancy: Likelihood that the condition is malignant.
    • Other properties (e.g. "urgentReferral", "pigmentedLesion") might also appear.
"clinicalIndicators": {
"resourceType": "Observation",
"component": [
{
"code": {
"coding": [
{
"code": "hasCondition",
"display": "Has condition"
}
]
},
"valueQuantity": {
"value": 100.0,
"unit": "%"
}
},
...
]
}
  • analysisDuration: A numeric value in seconds showing how long the system took to analyze the images.
  • observations: An array of observations that reference each media file examined:
    • media.resourceType: Always "Media".
    • media.content.url: Points to the specific image or file.
    • media.modality: For example, "Clinical".
    • media.diqaScore: The Dermatology Image Quality Assessment (DIQA) score for the image.
"observations": [
{
"media": {
"resourceType": "Media",
"content": { "url": "..." },
"type": "Image",
"modality": "Clinical",
"diqaScore": 86.0
}
},
...
]
  • conclusions: Represents the list of possible conditions identified, each with a probability and standardized coding:
"conclusions": [
{
"pathology": {
"coding": [
{
"code": "Psoriasis",
"display": "Psoriasis",
"system": "https://legit.health/integration/json-only/codes/conditions"
},
{
"code": "EA90",
"display": "Psoriasis",
"system": "ICD11"
}
]
},
"probability": 100.0
},
...
]
  • pathology.coding[0].code: The code that identifies the condition (e.g. "Psoriasis").
  • pathology.coding[0].display: Human-friendly label for the condition (e.g. "Psoriasis").
  • probability: The likelihood of this condition being correct, expressed as a percentage.

questionnaires

An array of QuestionnaireResponse objects, each containing details about a scoring system applied to the images:

"questionnaires": [
{
"resourceType": "QuestionnaireResponse",
"questionnaire": "APASI_LOCAL",
"scoringSystem": {
"text": "Local automatic psoriasis area and severity index",
"coding": [
{ "code": "APASI_LOCAL", "display": "Local automatic psoriasis area and severity index" }
]
},
"score": { ... },
"explainabilityMedia": { ... },
"item": [ ... ]
}
]
  • resourceType: Always "QuestionnaireResponse".
  • questionnaire: The internal code/name of the scoring system (e.g., "APASI_LOCAL").
  • scoringSystem: Contains the name and code for the scoring methodology.
  • score: The numeric outcome of the scoring system along with its severity interpretation.
  • explainabilityMedia: An optional annotated image that highlights how the score was determined.
  • item: An array of question-answer objects relevant to the scoring, each containing:
    • linkId or code: A reference to the specific question or assessment factor.
    • answer: The recorded response, often including a numeric level (e.g., “Moderate (2)”).

Overwriting callback URL

During the configuration process, the integrator must provide a callback URL to which to send the diagnostic reports. This URL can be customized at the iframe level through the parameter companyCallbackUrl, for example:

https://iframe.legit.health/?company=XXX&companyCallbackUrl=http://someserver.dev

How to request the report on demand

You can request the report on demand using our API.

https://back-{pre}.legit.health/s2s-api/v2/anonymous-diagnostic-reports/{encryptedId}?format=pdf&locale=es|en

You can find the endpoint documentation here: getAnonymousDiagnosticReport.

  • To choose the report locale, include the query parameter locale with the value es for Spanish or en for English.
  • To choose the report format, include the query parameter format with one of the following values: fhir, raw or pdf.

Scoring systems

Work in progress

This is a work in progress

Examples

Case in which confidence in the diagnosis has exceeded the threshold

Download

You can download an example of the JSON from this link: download (raw version) or download (FHIR version)

This case corresponds to the upload of 3 images of psoriasis on the elbow. In this case, our diagnostic support algorithm has calculated that it is psoriasis with high confidence, so the application displays the associated questionnaire (PASI) to calculate the severity.

The important thing here is that, although the user has uploaded 3 photos, the severity of the injury can only be calculated on one of them for logical reasons: each of the 3 photos can show different perspectives of the injury by omitting or adding parts, Therefore, we choose the most representative one for the calculation of severity.

Once the user completes the questionnaire, you receive a JSON like the one I attached where the most important fields are:

  • "observations", an array containing the images that has been uploaded by the user.
  • "result.scoringSystems" / "result.questionnaires": an array of questionnaires that contains the severity of the most probable condition.

Case in which confidence in the diagnosis has not exceeded the threshold

Download

You can download an example of the JSON from this link: download (raw version) or download (FHIR version)

In this case, the user has uploaded 3 images but our prediagnosis algorithm has not been able to determine the type of injury with a confidence that exceeds the threshold we have defined. Therefore, the severity is not calculated.

In the example JSON all the images are found under the "observations" property since the severity has not been calculated and, therefore, the field "result.scoringSystems" / "result.questionnaires" is empty.

Messages posted from iframe

Integration instructions

iOS

The iframe will send messages via the webkit bridge. You can listen for these messages to close the iframe or direct the user elsewhere in your application.

Below is the corresponding Swift code that allows you to display the iframe in your app and respond to the events posted.

struct WebView: UIViewRepresentable {
@Binding var path: NavigationPath

class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {
var webView: WKWebView?
var onMessageReceived: (Any) -> Void

required init(onMessageReceived: @escaping (Any) -> Void) {
self.onMessageReceived = onMessageReceived
super.init()
}

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView = webView
}

// Receive message from WKWebView
func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
print(message.body)
self.onMessageReceived(message.body)
}
}

func onMessageReceived(body: Any) -> Void {
DispatchQueue.main.async {
path.append(body as! String)
}
}

func makeCoordinator() -> Coordinator {
return Coordinator(onMessageReceived: onMessageReceived)
}

func makeUIView(context: Context) -> WKWebView {
let coordinator = makeCoordinator()
let userContentController = WKUserContentController()
userContentController.add(coordinator, name: "bridge")

let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController

let _wkwebview = WKWebView(frame: .zero, configuration: configuration)
_wkwebview.navigationDelegate = coordinator

return _wkwebview
}

func updateUIView(_ webView: WKWebView, context: Context) {
let companyIdentifier = AppConfig.companyIdentifier
let request = URLRequest(url: URL(string: "https://iframe.legit.health/?company=" + companyIdentifier)!)

webView.load(request)
}
}

This Swift code creates a WebView struct that conforms to UIViewRepresentable, allowing for communication between your web content and native app through the bridge message handler. The WebView's Coordinator class listens for events from the iframe and responds to them accordingly.

Android

In case you are developing an Android application, you should pass an object called legitHealthJsObject to our iframe using WebViewCompat

You have also to give the right permissions to the application, so the camera and file selector is displayed properly. You can view an example in this repository:

https://github.com/Legit-Health/android-iframe-integration

Analysis Completed Message

When the analysis of the image has been completed and the result is available, the iframe will post a message through three different channels depending on the type of integration.

The posted message contains the anonymousDiagnosticReportId, which is the identifier of the generated diagnostic report. You can use it to retrieve the complete report by consuming the endpoint described here: getting an Anonymous Diagnostic Report.

Web

In case you are embedding our iframe in a web-based application, the iframe will send a message via the postMessage after the image is processed.

window.parent.postMessage(
{
message: "analysis_completed",
id: "anonymousDiagnosticReportId",
},
"*"
);

You can listen for this message with a regular event listener:

window.addEventListener("message", function (event) {
if (event.data.message !== "analysis_completed") {
return;
}
console.log(event.data.id);
});

iOS

The iframe will send a message via the webkit bridge after the image is processed.

if (window.webkit?.messageHandlers?.bridge) {
window.webkit.messageHandlers.bridge.postMessage(
JSON.stringify({
message: "analysis_completed",
id: "anonymousDiagnosticReportId",
})
);
}

Android

The iframe will send a message via the legitHealthJsObject after the image is processed.

if (window.legitHealthJsObject) {
window.legitHealthJsObject.postMessage(
JSON.stringify({
message: "analysis_completed",
id: diagnosticReportId,
})
);
}

Invalid Company Key

When the iframe cannot be loaded because of an invalid company key, the iframe will post a message through three different channels depending on the type of integration.

Web

In case you are embedding our iframe in a web-based application, the iframe will send a message via the postMessage if the company key is invalid.

window.parent.postMessage(
{
message: "invalid_company_key",
id: companyKey,
},
"*"
);

iOS

The iframe will send a message via the webkit bridge if the company key is invalid.

if (window.webkit?.messageHandlers?.bridge) {
window.webkit.messageHandlers.bridge.postMessage(
JSON.stringify({
message: "invalid_company_key",
id: companyKey,
})
);
}

Android

The iframe will send a message via the legitHealthJsObject if the company key is invalid.

if (window.legitHealthJsObject) {
window.legitHealthJsObject.postMessage(
JSON.stringify({
message: "invalid_company_key",
id: companyKey,
})
);
}

Of course! Here’s a similar documentation for the image_rejected message:

Image Rejected

When an image is rejected due to insufficient quality, the iFrame will post a message indicating the rejection through three different channels depending on the type of integration.

Web

In case you are embedding our iframe in a web-based application, the iframe will send a message via the postMessage if the image is rejected.

window.parent.postMessage(
{
message: "image_rejected",
id: "",
payload: [
{
position: 0,
name: "hasEnoughQuality",
pass: false,
diqaScore: 30,
},
],
},
"*"
);

iOS

The iframe will send a message via the webkit bridge if the image is rejected.

if (window.webkit?.messageHandlers?.bridge) {
window.webkit.messageHandlers.bridge.postMessage(
JSON.stringify({
message: "image_rejected",
id: "",
payload: [
{
position: 0,
name: "hasEnoughQuality",
pass: false,
diqaScore: 30,
},
],
})
);
}

Android

The iframe will send a message via the legitHealthJsObject if the image is rejected.

if (window.legitHealthJsObject) {
window.legitHealthJsObject.postMessage(
JSON.stringify({
message: "image_rejected",
id: "",
payload: [
{
position: 0,
name: "hasEnoughQuality",
pass: false,
diqaScore: 30,
},
],
})
);
}