Medication Box Integrations / Smart Drug Box API

AngelTrack has an API by which your smart drug-boxes can pull medications-used data directly from the PCR.

If you use smart drug-boxes to track your narcotics, the boxes can integrate with AngelTrack to pull live PCR meds-given data straight from AngelTrack.

This API is available in AngelTrack build 516.

Narcotics

API Key Required

AngelTrack's medication-box API requires an API key, which you can issue from the API Configuration page under Settings.

To learn more about API keys, visit the API Keys Guide.

Configuration Checklist

To use the drug-box API, perform the following steps:

  1. Issue an API key with "Medication Box" privileges.
  2. Input the API key into your drug boxes, and configure the API URL for your AngelTrack server, which is:
    https://your_server_name.angeltrack.com/api/medications/MedicationsForDispatch
  3. Review your Medications List to ensure that it is up-to-date, and that all appropriate records are flagged as ☑ Narcotic and as ☑ Requires dose identification.
  1. Train crews how to input their dispatch ID into the drug box's user interface to retrieve the medications data for their trips.

API Implementation

If you are a drug-box maker, follow these instructions to consume AngelTrack's medication-box API.

You must collect a server-name from the EMS provider. Their AngelTrack server might be named "acme.angeltrack.com", so you must collect the "acme" part; the rest of the URL never changes, and in this example will be:

https://acme.angeltrack.com/api/medications/MedicationsForDispatch/xxxx

...where "xxxx" is an AngelTrack dispatch ID.

Crew members using a drug box in the field will always know the appropriate dispatch ID, since it appears plainly at the top of every PCR page. The interface provided to the crew members should say something like:

PCR server: https://acme.angeltrack.com/

Input dispatch ID: ____________

[ GO button ]

It shouldn't be necessary to collect any additional data from the crews.

The API is available 24/7, subject to AngelTrack's weekly maintenance windows.

Calling the API

The API only supports GET.

All requests must include a header field named "Accept" set to one of these two options, depending on how you prefer your reply data to be serialized:

application/xml; charset=utf-8
application/json; charset=utf-8

Authentication

In addition to the aforementioned content type header, every API request must include a header field named "Authorization", containing an authentication payload which looks like this:

Bearer [API key]

The "API key" is an API key which you can issue and retrieve from AngelTrack's UI under Settings. It is a UUID. Include the dash characters but do not include any curly braces or whitespace. Here is an example of a completed Authorization header:

Bearer F0BBC854-9222-486F-A76B-658FCF9E06AA

The provider has the power to change or revoke an API key whenever they wish, so your API key might stop working if they choose to disconnect you from the API.

To learn more about AngelTrack API keys, visit the API Key Guide.

Testing the API

To test the API and your key, send a GET request to the /MedicationsForDispatch URL for trip ID zero, like this:

GET https://your_server_name.angeltrack.com/api/medications/MedicationsForDispatch/0

Your GET request must include the "Authorization" header as discussed above.

The reply will be the standard data structure as discussed in the "Reply Data" section below, with its TripXML property set NULL because zero is never a valid dispatch ID.

Do you need a sandbox for testing?

If you are implementing this API but are not partnered with a specific AngelTrack customer with whom you can run tests, please contact AngelTrack headquarters, so that we can set you up with a sandbox server.

Reply Data

You will always receive back an HTTP status code 200 unless something went seriously wrong.

All lesser errors will still return HTTP status code 200, with diagnostic information returned inside the reply body.

The reply body is always a serialization of this C# structure:

public class MedicationsForDispatchReplyData
{
    /// <summary>
    /// Contains the AngelTrack build number of the answering server, for use in the
    /// future for deciding whether certain API functions are available.
    /// </summary>
    public int AngelTrackBuildNumber { get; set; } = 0;

    /// <summary>
    /// Contains the unique integer ID of the dispatch record in AngelTrack that was created or updated.
    /// Contains zero if there was any error.
    /// If you need a globally unique permanent ID for the trip, it's in the reply XML, in the @UUID attribute of the PatientCareReport node.
    /// </summary>
    public int DispatchID { get; set; } = 0;

    /// <summary>
    /// Contains 200 upon success, in which case there will be at least one message in the StatusMessages
    /// property. There might be a message in the ErrorMessages property if a tolerable error occurred, 
    /// such as if you send a trip which is to be assigned to a vehicle callsign that AngelTrack does not
    /// recognize.
    /// The API never returns any HTTP code in the range 201-299.
    /// Contains an HTTP status code between 300 and 599 to indicate a serious error that wholly prevents
    /// the processing of the request.
    /// </summary>
    [Range(200, 599)]
    public int EquivalentHttpStatusCode { get; set; } = 200;

    /// <summary>
    /// When EquivalentHttpStatusCode == 200, this will contain one or more more informational messages
    /// about the processing of the request.
    /// When EquivalentHttpStatusCode >= 300, this may be empty, or it may contain some informational messages
    /// subordinate to the error(s) reported in the ErrorMessages property.
    /// </summary>
    public System.Collections.Generic.List<String> StatusMessages { get; set; } = new System.Collections.Generic.List<String>();

    /// <summary>
    /// Contains any error messages that should be displayed to the end user.
    /// When EquivalentHttpStatusCode >= 300, this will contain at least one explanation.
    /// When EqiuvalentHttpStatusCode == 200, this may be empty, or it may contain reports of tolerable errors.
    /// </summary>
    public System.Collections.Generic.List<String> ErrorMessages { get; set; } = new System.Collections.Generic.List<String>();

    /// <summary>
    /// Contains a NEMSIS v3.5.0 XML document that has only three data notes: /DemographicGroup, /eMedications, and /eCustomResults.
    /// </summary>
    public String TripXML { get; set; } = null;

}

Remember you can request either JSON or XML serialization.

If you're requested a real trip ID, and if the trip ID is valid, and is not more than 14 days old, then TripXML will contain a partial NEMSIS XML document, containing the minimum necessary data nodes.

The XML data will be valid according to the NEMSIS XSDs, except that most other data nodes will be missing, so you will not be able to validate the document using XSDs.

TripXML Contents

The NEMSIS spec does not have built-in fields for vial-tracking, so instead, vials data must appear in the /eCustomResults node.

All NEMSIS PCR applications are free to define whatever /eCustomResults data format they like... but for the sake of everyone's sanity, AngelTrack emulates ImageTrend's standard narcotics-tracking fields which are named itControlledSubstances.

You can find documentation about those fields by looking in the NEMSIS TAC's custom-fields library.

Here is a sample reply, which contains the following records of meds handled by a Texas EMT whose patch number is TX-EMT-123456:

  • Aspirin, given, no vial ID
  • Fentanyl, unable to complete, wasted, with vial ID
  • Diazepam, unable to complete (not given), not wasted, thus no vial ID
  • Ketamine, given, with vial ID
<?xml version="1.0" encoding="utf-8"?>
<EMSDataSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nemsis.org http://nemsis.org/media/nemsis_v3/release-3.5.0/XSDs/NEMSIS_XSDs/EMSDataSet_v3.xsd" xmlns="http://www.nemsis.org">
    <Header>
        <DemographicGroup>
            <dAgency.01>123456789</dAgency.01>
            <dAgency.02>123456789</dAgency.02>
            <dAgency.04>48</dAgency.04>
        </DemographicGroup>
        <PatientCareReport UUID="D7A5636C-A9C5-488F-B6B6-AD808F10877F">
            <eMedications>
                <eMedications.MedicationGroup CorrelationID="ATMedication10662">
                    <eMedications.01>2022-07-27T17:32:46-00:00</eMedications.01>
                    <eMedications.02>9923001</eMedications.02>
                    <eMedications.03 PN="8801023" CodeType="9924003">4337</eMedications.03>
                    <eMedications.04>9927015</eMedications.04>
                    <eMedications.DosageGroup>
                        <eMedications.05>100</eMedications.05>
                        <eMedications.06>3706015</eMedications.06>
                    </eMedications.DosageGroup>
                    <eMedications.07 NV="7701001" xsi:nil="true" />
                    <eMedications.08 NV="7701001" xsi:nil="true" />
                    <eMedications.09>TX-EMT-123456</eMedications.09>
                    <eMedications.10>9905019</eMedications.10>
                    <eMedications.11>9918005</eMedications.11>
                </eMedications.MedicationGroup>
                <eMedications.MedicationGroup CorrelationID="ATMedication10663">
                    <eMedications.01>2022-07-27T17:32:46-00:00</eMedications.01>
                    <eMedications.02>9923001</eMedications.02>
                    <eMedications.03 PN="8801023" CodeType="9924003">3322</eMedications.03>
                    <eMedications.04>9927023</eMedications.04>
                    <eMedications.DosageGroup>
                        <eMedications.05>100</eMedications.05>
                        <eMedications.06>3706021</eMedications.06>
                    </eMedications.DosageGroup>
                    <eMedications.07>9916003</eMedications.07>
                    <eMedications.08 NV="7701001" xsi:nil="true" />
                    <eMedications.09>TX-EMT-123456</eMedications.09>
                    <eMedications.10>9905019</eMedications.10>
                    <eMedications.11>9918005</eMedications.11>
                </eMedications.MedicationGroup>
                <eMedications.MedicationGroup CorrelationID="ATMedication10664">
                    <eMedications.01>2022-07-27T17:32:46-00:00</eMedications.01>
                    <eMedications.02>9923001</eMedications.02>
                    <eMedications.03 CodeType="9924003">6130</eMedications.03>
                    <eMedications.04>9927047</eMedications.04>
                    <eMedications.DosageGroup>
                        <eMedications.05>150</eMedications.05>
                        <eMedications.06>3706021</eMedications.06>
                    </eMedications.DosageGroup>
                    <eMedications.07>9916001</eMedications.07>
                    <eMedications.08 CorrelationID="ATMedication10664C1">3708007</eMedications.08>
                    <eMedications.09>TX-EMT-123456</eMedications.09>
                    <eMedications.10>9905019</eMedications.10>
                    <eMedications.11>9918005</eMedications.11>
                </eMedications.MedicationGroup>
                <eMedications.MedicationGroup CorrelationID="ATMedication10665">
                    <eMedications.01>2022-07-27T17:32:46-00:00</eMedications.01>
                    <eMedications.02>9923001</eMedications.02>
                    <eMedications.03 CodeType="9924003">1191</eMedications.03>
                    <eMedications.04>9927035</eMedications.04>
                    <eMedications.DosageGroup>
                        <eMedications.05>200</eMedications.05>
                        <eMedications.06>3706021</eMedications.06>
                    </eMedications.DosageGroup>
                    <eMedications.07>9916001</eMedications.07>
                    <eMedications.08 CorrelationID="ATMedication10665C1">3708031</eMedications.08>
                    <eMedications.09>TX-EMT-123456</eMedications.09>
                    <eMedications.10>9905019</eMedications.10>
                    <eMedications.11>9918005</eMedications.11>
                </eMedications.MedicationGroup>
            </eMedications>
            <eCustomResults>
                <eCustomResults.ResultsGroup CorrelationID="ATMedication10662">
                    <eCustomResults.01>itControlledSubstancesGroup</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstancesGroup</eCustomResults.02>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>itControlledSubstances.02.101</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.002</eCustomResults.02>
                    <eCustomResults.03>ATMedication10662</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>Fentanyl</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.009</eCustomResults.02>
                    <eCustomResults.03>ATMedication10662</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>100</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.012</eCustomResults.02>
                    <eCustomResults.03>ATMedication10662</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>VIALFENT67890</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.013</eCustomResults.02>
                    <eCustomResults.03>ATMedication10662</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup CorrelationID="ATMedication10664">
                    <eCustomResults.01>itControlledSubstancesGroup</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstancesGroup</eCustomResults.02>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>itControlledSubstances.02.100</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.002</eCustomResults.02>
                    <eCustomResults.03>ATMedication10664</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>Ketamine</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.009</eCustomResults.02>
                    <eCustomResults.03>ATMedication10664</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>150</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.011</eCustomResults.02>
                    <eCustomResults.03>ATMedication10664</eCustomResults.03>
                </eCustomResults.ResultsGroup>
                <eCustomResults.ResultsGroup>
                    <eCustomResults.01>KET11223344</eCustomResults.01>
                    <eCustomResults.02>itControlledSubstances.013</eCustomResults.02>
                    <eCustomResults.03>ATMedication10664</eCustomResults.03>
                </eCustomResults.ResultsGroup>
            </eCustomResults>
        </PatientCareReport>
    </Header>
</EMSDataSet>

Be warned: The /eMedications node can include meds given by first-responders and by other people not among the crew, which you’ll want to filter out. To do that, exclude any /eMedications.MedicationGroup that has no eMedications.09 node, or whose eMedications.02 node = 9923003.

You will also want to inspect eMedications.03/@PN so that you can ignore other dud events, such as "Not given - Contraindicated", "Patient refused", and so forth.

"No Records" Case

If the PCR has no medications recorded, it must nevertheless always emit one eMedications.MedicationGroup node, because the NEMSIS spec does not mark that node as "minOccurs=0".

PCRs work around this problem by emitting a pseudo-node like this:

<eMedications>
    <eMedications.MedicationGroup>
        <eMedications.01 NV="7701001" xsi:nil="true" />
        <eMedications.02 NV="7701001" xsi:nil="true" />
        <eMedications.03 NV="7701001" xsi:nil="true" />
        <eMedications.04 NV="7701001" xsi:nil="true" />
        <eMedications.DosageGroup>
            <eMedications.05 NV="7701001" xsi:nil="true" />
            <eMedications.06 NV="7701001" xsi:nil="true" />
        </eMedications.DosageGroup>
        <eMedications.07 NV="7701001" xsi:nil="true" />
        <eMedications.08 NV="7701001" xsi:nil="true" />
        <eMedications.09 NV="7701001" xsi:nil="true" />
        <eMedications.10 NV="7701001" xsi:nil="true" />
    </eMedications.MedicationGroup>
</eMedications>

...where every field has got a null-value equal to "Not applicable". At least that is how AngelTrack does it; other PCRs might send "Not recorded" instead. In any event, the pseudo-node will contain no meaningful data, so be sure that your filtering code knows to exclude such nodes.

Retrieving the Globally Unique Trip ID

Among the housekeeping fields you get back will be the trip’s dispatch ID, which is the same ID number that you passed-in to the API in your request.

You can also retrieve the trip’s UUID, a globally unique unchanging trip ID, by digging into its XML for this attribute:

/EMSDataSet/Header/PatientCareReport@UUID

In AngelTrack, dispatch IDs never change, nor do UUIDs.

Request Throttles

The medication-box API is throttled to 24 requests per provider in any two-hour period.

If the provider's crew members exceed the throttle, you will get back this error message:

Flood protection. Please wait before trying again.

...in the reply, with EquivalentHttpStatusCode = 429.

DO NOT POLL THIS API. It is for use only for downloads initiated by the crew member.

Deleted PCR Records / Primary Keys

AngelTrack's PCR records have soft-deletion, and so a record you previously downloaded might vanish from a subsequent download, then reappear again later, as the attending makes adjustments to the chart.

The time-administered field could also change; in fact any of the datafields could change.

To minimize the amount of mix-and-match you must do, counsel the crew members to download their medication-box data after they've finished their chart in AngelTrack.

That said, one data element that will never change is the record-ID value in:

eMedications.MedicationGroup/@CorrelationID

Its value looks like this: ATMedication123456

...where 123456 is the unique unchanging ID number of a PCR-Meds record in AngelTrack. That ID will suffice as a primary key when merging an earlier download with a later download.

In the unlikely event that you pull data from multiple AngelTrack servers, you could have two completely different records whose ID numbers are ATMedication123456. In that case, you must include the AngelTrack server name as an element of your primary key, like this for a record downloaded from acme.angeltrack.com:

acme:ATMedication123456

...or else include the UUID of the trip record.