Skip to main content
A listing is the public face a pharmacy chooses to show: its name, address, opening hours, a pin on the map, and a rough answer to “do you have this medicine?”. Think of a “find a pharmacy near me” feature in a patient app, or a purchasing tool that wants to know where a medicine can be found today. Nothing here is patient information, and nothing here exposes an exact stock number. A pharmacy’s availability is a band (out, low, in_stock or unknown), always with the time it was true. See Stock bands for what each one means.

Which pharmacies can you see?

You never see “all pharmacies”. You see the pharmacies that have chosen to be listed and have agreed to a connection with your organization that includes the permission you are using. How a connection is made, and what a pharmacy can grant, is on Connections. In short:
  • A live organization key sees the pharmacies connected to it.
  • A test key sees your workspace’s own seeded, fictional pharmacies, so you can build before any real pharmacy has connected.
  • A clinic’s own key gets an empty result from the network routes (/v1/listings/pharmacies and /v1/listings/search). A clinic reads its own data, not a network of other clinics.
  • A pharmacy that is not listed, or an item it has excluded from its listing, never appears. There is no way to ask for it.
Two things appear in several responses, so learn them once:
  • band is out, low, in_stock or unknown. unknown means the pharmacy’s own till has not reported recently enough to trust, so we say so rather than show a stale answer. Show it as its own state, never as out. See Stock bands.
  • as_of is when that band was last known to be true. Show it. A band without its age looks fresher than it is.

List listed pharmacies

Requires listing.profile:read. Returns each pharmacy’s profile (no stock), a page at a time.

Search for a medicine

Requires listing.availability:read. One request searches only the pharmacies you can reach. Results are capped at 50 and are not paginated: narrow the search instead. Any other field in the body is refused with 400 and the field’s name.
  • With near, nearest pharmacies come first. A pharmacy with no pin is not removed by radius_km: it has distance_km: null and comes last.
  • A test key matches identifiers gtin and nafdac, and names (q). ndc, rxcui and partner identifiers find nothing in the sandbox and return an empty data list.

Tell us whether a result was right

Requires listing.availability:read. After you (or your user) find out whether a search result was true, report it. The report holds no user data: only the item you searched for, optionally the pharmacy, and the outcome.
id is the id of your report. With a test key the request is checked and answered with an id, but nothing is stored, because a sandbox pharmacy is not a real one. An unknown outcome, or a missing item, is refused with 400.

One pharmacy’s items

Requires listing.availability:read. Use this when you already hold a connection to the pharmacy and want all of its listed items with their bands, for example to fill a local cache. It goes through the same per-clinic checks as Inventory. Only items the pharmacy has chosen to list appear.
A test key reaches only your sandbox’s own pharmacy: the clinic_id that GET /v1/me reports.

One pharmacy’s profile

Requires listing.profile:read. The same profile fields as the pharmacy list, for one pharmacy. The response is the profile itself, with no data wrapper.
It has the fields in the table above, except open_now, plus:

Things to know

  • You get what the pharmacy allowed, and no more. There is no exact quantity, price, cost or customer information on any of these routes.
  • A pharmacy you cannot reach looks like a pharmacy that does not exist. On the two per-pharmacy routes the answer is 404 with code: "not_found": no connection, a revoked connection, or a wrong id are never told apart.
  • A missing permission is 403 with code: "scope_missing". The pharmacy granted your connection some permissions but not this one. See Permissions.
  • Bad input is 400 with code: "invalid_request", with an errors list naming each field. An unknown query parameter is refused too. All error codes are on Errors.
  • Rate limits are per key. Network searches count like any other request. Read RateLimit-Remaining and see Rate limits.
  • When something changes you can be told instead of asking again. See Events.

Recipes

Show “pharmacies near me that have this medicine”.
  1. POST /v1/listings/search with the medicine’s barcode ({ "system": "gtin", … }) if you have one, otherwise its name, plus the user’s near point.
  2. Show in_stock and low first, and show unknown as “may be out of date”, never as out of stock. Show as_of next to each.
  3. When the user confirms or contradicts a result, send POST /v1/listings/feedback with the matching outcome.
Build a pharmacy directory. Page through GET /v1/listings/pharmacies until has_more is false, and store each pharmacy by clinic_id. To refresh one pharmacy later, read its profile and compare updated_at. Keep a local copy of one pharmacy’s catalogue. Read GET /v1/clinics/{clinic_id}/listing/items once, then call it again with updated_since set to the time of your last read. Treat each band as only as fresh as its as_of.