VaccinateCA

API documentation

The goal is to update this documentation as part of any commit that modifies how the API works in any way.

The base URL for every API is https://vial.calltheshots.us/

VIAL admin utilities

GET /admin/edit-location/<public_id>/

Not a JSON API, but this is a convenient way to link to the edit page for a specific location. You can contruct this URL with the public ID of the location and VIAL will redirect the authenticated user to the corresponding edit interface for that location.

Search APIs

GET /api/searchLocations

Search all of our locations, excluding those that have been soft-deleted.

Optional query string parameters:

The following output formats are supported:

You can also add debug=1 to the JSON output to wrap them in an HTML page. This is primarily useful in development as it enables the Django Debug Toolbar for those results.

GET /api/searchSourceLocations

Source locations are "raw" location data that has been imported into VIAL by one of our data ingestion flows. We do not expose these to end-users - we instead use them as part of our internal processes for identifying new vaccination sources and turning those into public locations.

Optional query string parameters:

As with /api/searchLocations you can add debug=1 to the URL if you are working with the Django Debug Toolbar.

Some examples:

The summary format returns newline-delimited JSON of just the source_uid, matched_location_id (as a public location ID) and content_hash fields:

APIs used by our Scooby and Velma apps

POST /api/submitReport

This API records a new "report" in our database. A report is when someone checks with a vaccination location - usually by calling them - to find out their current status.

You call this API by HTTP POST, sending JSON as the POST body. A valid Auth0 JWT should be included in a Authorization: Bearer JWT-GOES-HERE HTTP header.

The JSON document should have the following keys:

Optional fields:

Here is an example submission:

{
    "Location": "recgDrq7aQMo0M5x7",
    "Appointment scheduling instructions": "www.walgreens.com",
    "Availability": [
        "Yes: vaccinating 65+",
        "Yes: appointment required",
        "Vaccinating essential workers"
    ],
    "Notes": "Check the Walgreens site regularly to see when appointments open up.",
    "Internal Notes": ""
}

Availability tags

For backwards compatibility with the existing application, there is some degree of flexibility in accepting availability tags.

Ideally you would use the slug for a tag, for example only_staff for only vaccinating staff.

You can alternatively use the tag's full name, or one of the names contained in the "previous names" array.

A list of valid tags with their slugs, names and previous_names can be found at https://vial.calltheshots.us/api/availabilityTags

Skip requests

If a Do not call until timestamp is provided and one of the availability tags is "Call back later"/"Skip: call back later", a new call request is enqueued with a vesting time equal to the Do not call until timestamp. This handles reports like "Closed until Monday" while making sure that we don't drop the request.

An example of one of these requests:

{
  "Location": "rec5RXyYpi7IHQ2eN",
  "Availability":["Skip: call back later"],
  "Notes": "Feb 21: This location does not currently have vaccine available but may get stock in the coming weeks.", 
  "Internal Notes": "Feb 21: The pharmacy tech transferred me to the \"dispensing pharmacy\" as the best place to get questions answered.", 
  "Do not call until": "2021-03-07T18:35:03.742Z"
}

Return value

The API returns an indication that the record has been created, including the newly created record's public ID.

{
    "created": ["rec234252"]
}

It currently returns other debugging data (as exposed in the API explorer) but you should ignore this - it's there for debugging and is likely to be removed soon.

Debug mode

A tool for exercising this API is available at https://vial.calltheshots.us/api/submitReport/debug - if you have previously signed into the tool at https://vial.calltheshots.us/ the interface will be pre-populated with a valid JWT token. If that token has expired you can get a new one by signing in and out again.

POST /api/requestCall

Request a new location to call. This record will pick the request from the call queue with the highest priority and "lock" that record for twenty minutes, assigning it to your authenticated user.

HTTP POST, sending an empty {} JSON object as the POST body. A valid Auth0 JWT should be included in a Authorization: Bearer JWT-GOES-HERE HTTP header.

You can customize the results returned by this API by passing querystring parameters:

These are querystring parameters, so you should POST to /api/requestCall?state=OR while still sending an empty {} JSON object as the POST body.

The response from this API currently looks like this:


    "id": "lcyzg",
    "Name": "Fred Meyer Pharmacy #70100165",
    "Phone number": "(541) 884-1780",
    "Address": "2655 Shasta Way, Klamath Falls, OR, 97603",
    "Internal notes": null,
    "Hours": null,
    "County": "Klamath",
    "Location Type": "Unknown",
    "Affiliation": null,
    "Latest report": null,
    "Latest report notes": [
        null
    ],
    "County vaccine info URL": [
        null
    ],
    "County Vaccine locations URL": [
        null
    ],
    "Latest Internal Notes": [
        null
    ],
    "Availability Info": [],
    "Number of Reports": 0,
    "county_record": {
        "id": 2225,
        "County": "Klamath",
        "Vaccine info URL": null,
        "Vaccine locations URL": null,
        "Notes": null
    },
    "provider_record": {}
}

Try this API: https://vial.calltheshots.us/api/requestCall/debug

GET /api/verifyToken

Private API for testing our own API tokens (not the JWTs). Send an API key as the Authorization: Bearer API-KEY-GOES-HERE HTTP header.

Returns status 302 and an {"error": "message"} if the API key is invalid, otherwise returns:

{
    "key_id": 1,
    "description": "Description of the key",
    "last_seen_at": "2021-03-10T01:43:32.010Z"
}

POST /api/callerStats

Returns stats for the authenticated user.

HTTP POST, sending an empty {} JSON object as the POST body. A valid Auth0 JWT should be included in a Authorization: Bearer JWT-GOES-HERE HTTP header.

You can use GET here too.

The response currently looks like this:

{
  "total": 23,
  "today": 3
}

Try this API: https://vial.calltheshots.us/api/callerStats/debug

POST /api/requestTask

Request a task to be completed by a user. Tasks are a more generic concept than call requests - they are intended to grow to cover a variety of different data checking flows.

HTTP POST with a JWT token or API key.

The body sent with the POST must specify the type of task to be returned - see https://vial.calltheshots.us/api/taskTypes for a list:

{
  "task_type": "Potential duplicate"
}

It accepts optional arguments which will filter the tasks based on their location:

So to request a task for a Walgreens in Kansas (if one is available) you would use:

{
  "task_type": "Potential duplicate",
  "state": "KS",
  "q": "walgreens"
}

If there are no unresolved tasks of this type available the API will return the following (with a 200 status code):

{
  "task_type": "Potential duplicate",
  "task": null,
  "warning": "No unresolved tasks of type \"Potential duplicate\""
}

If there are tasks available, one will be selected at random and will be returned looking like this:

{
  "task_type": "Potential duplicate",
  "task": {
    "id": 5,
    "task_type": "Potential duplicate",
    "location": {
        "id": "rec5sJrYqJbKNO4N6",
        # ...
        "concordances": [
            "google_places:ChIJoXTs-acp6IARE2H4ljeSOQY",
            "on:twoe"
        ],
        "soft_deleted": false
    },
    "other_location": {
        "id": "rec52fj1wLHnVLwxq",
        # ...
        "soft_deleted": false
    },
    "details": {
        "details": "go here"
    }
  },
  "unresolved_of_this_type": 1
}

other_location can be null. Both location and other_location reuse the JSON format from the search API, which is unstable and may change in the future. They add one extra key to that representation, "soft_deleted": bool which can be used to indicate that a location has been soft deleted.

The details key can be null or a JSON object - its contents will differ for different task types.

The unresolved_of_this_type integer indicates how many unresolved tasks (including this one) are left in the queue.

Try this API: https://vial.calltheshots.us/api/requestTask/debug

POST /api/resolveTask

Mark a task as resolved. The specified task will be updated setting 'resolved_by' to the authenticated user (based on the JWT or API key). An optional resolution JSON object can also be passed, which will be stored in the corresponding database column.

{
  "task_id": 123,
  "resolution": {"optional": "json"}
}

If the task does not exist or has already been resolved, returns a 400 status code with an "error" message key:

{
  "error": [
    {
      "loc": [
        "task_id"
      ],
      "msg": "Task 123 is already resolved",
      "type": "value_error"
    }
  ]
}

If the API call succeeds, returns:

{
  "task_id": 123,
  "resolution": {"optional_json": "or null"},
  "resolved": true
}

Try this API: https://vial.calltheshots.us/api/resolveTask/debug

POST /api/mergeLocations

Merge two locations. Requires a JWT token with the write:locations permission or an API key.

Performs the same process as the https://vial.calltheshots.us/admin/merge-locations/ tool:

The POST body should include:

{
  "winner": "recXXX",
  "loser": "recYYY"
}

That's the public location IDs of the winner and loser of the merge.

An optional "task_id" can also be passed, if this merge operation was the result of a task. If a task ID is passed then that task will be marked as resolved.

The response from the API looks like this:

{
  "winner": {
    "id": "ltkqt",
    "name": "South Carolina Oncology Associates",
    "vial_url": "http://vial-staging.calltheshots.us/admin/core/location/46747/change/"
  },
  "loser": {
    "id": "ltkqp",
    "name": "South Carolina Oncology Associates",
    "vial_url": "http://vial-staging.calltheshots.us/admin/core/location/46743/change/"
  },
}

Try this API: https://vial.calltheshots.us/api/mergeLocations/debug

APIs used for getting data into VIAL

POST /api/startImportRun

POST an empty body to this at the beginning of a source location import run to get an import ID, needed for the calls to /api/importSourceLocations in order to tie everything together.

Returns the following JSON:

{
    "import_run_id": 2
}

Try this API: https://vial.calltheshots.us/api/startImportRun/debug

POST /api/importSourceLocations?import_run_id=X

POST this a newline-delimited list of JSON objects. The ?import_run_id parameter must be the ID of an import run you have previously created using POST /api/startImportRun.

Each newline-delimited JSON object should have the following shape:

Returns a 400 error on errors, a 200 on success.

Try this API: https://vial.calltheshots.us/api/importSourceLocations/debug

POST /api/importLocations

Private API for us to import new locations into the database - or update existing locations.

Accepts a POST with a JSON document with either a single location object or a list of location objects.

You'll need an API key, which you pass in the Authorization: Bearer API-KEY-GOES-HERE HTTP header. API keys can be created in the Django admin at https://vial.calltheshots.us/admin/api/apikey/

Each location should look like this:

{
    "name": "Walgreens San Francisco III",
    "state": "CA",
    "latitude": 37.781869,
    "longitude": -122.439517,
    "location_type": "Pharmacy",
}

Each of these fields is required.

The state value should be the two letter acronym for a state (or AS for American Samoa, GU for Guam, MP for Northern Mariana Islands, PR for Puerto Rico, VI for Virgin Islands or DC for District of Columbia).

The latitude and longitude should be floating point values.

The location_type should be one of the values shown on https://vial.calltheshots.us/api/locationTypes

There is also an optional import_ref key, described below.

The API returns the following:

{
    "errors": [],
    "added": ["lc", "ld"],
    "updated": ["lb"],
}

The following input fields are all optional strings:

If you are providing a county it must be the name of a county that exists within the provided state.

You can also specify a provider_name and a provider_type, if the location belongs to a chain of locations.

The provider_type must be one of the list of types from /api/providerTypes.

The provider_name will be used to either create a new provider or associate your location with an existing provider with that name.

If you provide the import_json dictionary it should be the original, raw JSON data that your importer script is working against. This will be stored in the import_json column in the locations table, and can later be used for debugging purposes.

Using import_ref to import and later update locations

If you are importing locations from another source that may provide updated data in the future, you can use the import_ref key to specify a unique import reference for the record.

If you call /api/importLocations again in the future with the same import_ref value, the record will be updated in place rather than a new location being created.

For example, submitting the following:

{
    "name": "Walgreens San Francisco III",
    "state": "CA",
    "latitude": 37.781869,
    "longitude": -122.439517,
    "location_type": "Pharmacy",
    "import_ref": "walgreens-scraper:1231"
}

Will assign an import_ref of walgreens-scraper:1231 to the record. If you later submit the following:

{
    "name": "Walgreens San Francisco",
    "state": "CA",
    "latitude": 37.781869,
    "longitude": -122.439517,
    "location_type": "Super Site",
    "import_ref": "walgreens-scraper:1231"
}

The existing record will be updated with those altered values.

Make sure you pick import refs that won't be used by anyone else: using a prefix that matches the location you are pulling from is a good idea.

Try this API: https://vial.calltheshots.us/api/importLocations/debug

POST /api/updateLocations

This API can be used to update fields on one or more locations. The POSTed JSON looks like this:

{
  "update": {
    "$location_id": {
      "$field1": "new_value",
      "$field2": "new_value",
      "$field3": "..."
    }
  },
  "revision_comment": "Optional comment"
}

In the above example, $location_id is the public location ID - a string such as rec9Zc6A08cEWyNpR or lgzgq.

This is a bulk API, so you can provide multiple nested location ID dictionaries.

Only the fields that you provide will be updated on the record - so unlike /api/importLocations with an import_ref this API allows partial updates of just specified fields.

The following example will set a new name on location rec9Zc6A08cEWyNpR and a new phone number on location lgzgq. It will also set a custom revision message (visible in the object history) of "New details".

{
  "update": {
    "rec9Zc6A08cEWyNpR": {
      "name": "Berkeley Clinic II"
    },
    "lgzgq": {
      "phone_number": "(555) 555-5551"
    }
  },
  "revision_comment": "New details"
}

The "revision_comment" key can be ommitted entirely, in which a default comment of "/api/updateLocations by API_KEY" will be used.

The following fields can be updated using this API. All are optional.

Try this API: https://vial.calltheshots.us/api/updateLocations/debug

POST /api/updateLocationConcordances

Bulk API for adding and removing concordances (IDs from other systems) to our locations.

The API is similar to /api/updateLocations. The input looks like this:

{
  "update": {
    "$location_id": {
      "add": [
        "$authority:$identifier"
      ]
    }
  }
}

To add a Google Places ID of ChIJsb3xzpJNg4ARVC7_9DDwJnU to the location with ID recfwh2p1fNN7TN4C you would send this:

{
  "update": {
    "recfwh2p1fNN7TN4C": {
      "add": [
        "google_places:ChIJsb3xzpJNg4ARVC7_9DDwJnU"
      ]
    }
  }
}

Note that the concordance ID references used here are always authority:identifier - where the authority is something like google_places or vaccinespotter or vaccinefinder.

A full list of authorities currently in use can be found on the concordance identifier listing page.

To remove a concordance identifier, use "remove" instead of "add":

{
  "update": {
    "recfwh2p1fNN7TN4C": {
      "remove": [
        "google_places:ChIJsb3xzpJNg4ARVC7_9DDwJnU"
      ]
    }
  }
}

You can pass multiple ID references to both the "add" and the "remove" action. You can send multiple location IDs to the endpoint at once.

Try this API: https://vial.calltheshots.us/api/updateLocationConcordances/debug

POST /api/updateSourceLocationMatch

API for updating a source location to mark it as being a confirmed match for a specific location. This modifies the source_location table to update the matched_location_id column, and records a history record with the old and new values in the source_location_match_history table.

Any concordance identifiers attached to the source location that are not yet attached to the location will be copied across.

Accepts either an API key or a JWT token for authentication.

POST the following JSON:

{
  "source_location": "vaccinespotter_org:206891330",
  "location": "lxyz"
}

Returns the following:

{
  "matched": {
    "location": {
      "id": "lxx",
      "name": "Location 1"
    },
    "source_location": {
      "source_uid": "test:1",
      "name": "Test location"
    }
  }
}

The returned "location" can be null if you deliberately un-set the match.

Try this API: https://vial.calltheshots.us/api/updateSourceLocationMatch/debug

POST /api/createLocationFromSourceLocation

Creates a new location record from an available source location.

Accepts either an API key or a JWT token for authentication.

POST the following JSON:

{
  "source_location": "vaccinespotter_org:206891330",
}

source_location should be a source_uid, but can alternatively be an internal database integer ID

Returns the following:

{
  "location": {
    "id": "lxx",
    "name": "Location 1",
    "vial_url": "https://vial.calltheshots.us/admin/core/location/1234/change/"
  }
}

Try this API: https://vial.calltheshots.us/api/createLocationFromSourceLocation/debug

POST /api/importReports

Private API for us to import old reports from Airtable into the VIAL database.

Accepts a JSON array of items from the airtable-data-backup/backups/Reports.json file.

Try this API: https://vial.calltheshots.us/api/importReports/debug

POST /api/importTasks

API for adding tasks to be completed by our volunteers. Authentication is either an API key of a JWT token.

Accepts either a single item or a list of items. Each item should look like this:

{
  "task_type": "Potential duplicate",
  "location": "recxxx",
  "other_location": "recyyy",
  "details": {
    "details": "go here"
  }
}

The only required fields are task_type and location. The other_location and details fields may be required depending on the task type.

See https://vial.calltheshots.us/api/taskTypes for a list of available task types.

Try this API: https://vial.calltheshots.us/api/importTasks/debug

POST /api/resolveMissingCounties

POSTing an empty body to this will cause VIAL to find 100 locations that are missing their county and attempt to resolve that county based on their latitude and longitude.

If the county cannot be resolved, VIAL will create a new task of type "Resolve county" against that location, and will not attempt to resolve the county for that location again in the future.

Miscellaneous read-only data APIs

GET /api/location/PUBLIC_ID/concordances

Retrieve the concordances (external identifiers) for a specific location.

Example output:

{
    "concordances": [
        "google_places:ChIJh1Vp2AhS2YAReWQls7L8uKY",
        "vaccinespotter:798382",
        "vaccinefinder:bdae81b3-d682-405a-b66e-50052e4a140a"
    ]
}

Try this API: https://vial.calltheshots.us/api/location/rec1wYYRF8RLX9B9y/concordances

GET /api/providerTypes

Unauthenticated. Returns a "provider_types" key containing a JSON array of names of valid provider types, e.g. Pharmacy.

Example output:

{
    "provider_types": [
        "Pharmacy",
        "Hospital",
        "Health Plan",
        "Other"
    ]
}

Try this API: https://vial.calltheshots.us/api/providerTypes

GET /api/locationTypes

Unauthenticated. Returns a "location_types" key containing a JSON array of names of valid location types, e.g. Pharmacy.

Example output:

{
    "location_types": [
        "Hospital / Clinic",
        "Pharmacy",
        "Super Site",
        "Private Practice",
        "School",
        "Other",
        "Nursing home",
        "Urgent care",
        "Dialysis clinic",
        "Health department",
        "Santa Barbara County Juvenile Hall",
        "Mobile clinic",
        "Specialist",
        "Ambulance",
        "In-home Senior Care",
        "Mental Health",
        "Rehabilitation Center",
        "First Responder",
        "Shelter",
        "Unknown"
    ]
}

Try this API: https://vial.calltheshots.us/api/locationTypes

GET /api/taskTypes

Unauthenticated. Returns a "task_types" key containing a JSON array of names of valid task types, e.g. Potential duplicate.

Example output:

{
    "task_types": [
        "Potential duplicates"
    ]
}

Try this API: https://vial.calltheshots.us/api/taskTypes

GET /api/availabilityTags

Unauthenticated. Returns a "availability_tags" key containing a JSON array of availability tags.

Example output:

{
  "availability_tags": [
    {
      "slug": "only_staff",
      "name": "Only vaccinating staff",
      "group": "no",
      "notes": "This location is currently only vaccinating their own staff",
      "previous_names": [
        "No: only vaccinating staff"
      ]
    },
    {
      "slug": "not_open_to_the_public",
      "name": "Not open to the public",
      "group": "no",
      "notes": "This location is currently not open to the public",
      "previous_names": [
        "No: not open to the public"
      ]
    },
    {
      "slug": "only_health_care_workers",
      "name": "Only vaccinating health care workers",
      "group": "no",
      "notes": "This location is currently only vaccinating healthcare workers",
      "previous_names": [
        "No: only vaccinating health care workers"
      ]
    }
  ]
}

Try this API: https://vial.calltheshots.us/api/availabilityTags

GET /api/counties/<state>

Unauthenticated. Returns a list of counties for the two-letter state code.

{
  "state_name": "Rhode Island",
  "state_abbreviation": "RI",
  "state_fips_code": "44",
  "counties": [
    {
      "county_name": "Bristol",
      "county_fips_code": "44001"
    },
    {
      "county_name": "Kent",
      "county_fips_code": "44003"
    },
    {
      "county_name": "Newport",
      "county_fips_code": "44005"
    },
    {
      "county_name": "Providence",
      "county_fips_code": "44007"
    },
    {
      "county_name": "Washington",
      "county_fips_code": "44009"
    }
  ]
}

Examples:

APIs for exporting data

POST /api/exportMapbox

Uploads the location JSON to Mapbox.

GET /api/exportMapboxPreview

Preview the JSON that we generate for Mapbox. This returns 20 recent locations by default, or use one or more ?id=public_id parameters to see specific locations.

Add &raw=1 to get back just the raw JSON, without it being wrapped in HTML. Use &export=1 to simulate the export filter logic - which means you'll get back a blank document if the location would not be exported to Mapbox due to logic such as "don't export locations with a close date in the past".

GET /api/exportPreview/Locations.json

Debugging endpoint showing a preview of the Locations.json feed generated by VIAL.

Defaults to returning 10 locations. You can also feed it multiple ?id=recxxx public location IDs to see those specific location representations.

Try this API: https://vial.calltheshots.us/api/exportPreview/Locations.json

GET /api/exportPreview/Providers.json

Debugging endpoint showing a preview of the Providers.json feed generated by VIAL.

Defaults to returning 10 providers. You can also feed it multiple ?id=recxxx public location IDs to see those specific provider representations.

Try this API: https://vial.calltheshots.us/api/exportPreview/Providers.json