Each prescription in an order names one medication by its med_ ID. The medication tells you what
its pharmacy accepts. Read it before you send an order.
Find a medication
List medications for the mode of your key:
curl "https://api.affinityrx.com/v1/medications?limit=10" \
-H "Authorization: Bearer $AFFINITY_API_KEY" \
-H "Affinity-Version: 2026-10-28"Test offers each pharmacy’s Live medications with the same medication IDs, names and prices.
Medication and shipping option IDs are shared across Test and Live. Old Test shipping IDs remain valid in Test.
The list contains all the medications that you can order in the mode of your key, A to Z by name.
It has no search and no filters. To get the next page, send nextCursor as startingAfter. Stop
when hasMore is false. This list goes forward only. limit sets the number of results on one
page: 1 to 100. The default is 25. practiceId quotes prices for one practice; see
Prices for your practice. The list accepts only practiceId, limit
and startingAfter. Any other query parameter returns 400 invalid_request.
To find a medication, read all the pages. Then select it by name, strength, form and
pharmacy. Do not select the first result of a page.
Each medication has a category, one of these values:
| Value | Medications for |
|---|---|
weight_loss | Weight loss |
hormones | Hormones |
sexual_health | Sexual health |
hair | Hair |
skin | Skin |
wellness | Wellness |
peptides | Peptides |
pain | Pain |
other | Other |
supplies | Supplies |
Every strength, form and pharmacy of one medication has the same category.
To read one medication, send GET /v1/medications/{medicationId}.
Read the other strengths of a medication
A medication ID names one strength, one form and one pharmacy. To read the other strengths, forms
and pharmacies of the same medication in one request, add expand=variants:
curl "https://api.affinityrx.com/v1/medications/$MEDICATION_ID?expand=variants" \
-H "Authorization: Bearer $AFFINITY_API_KEY" \
-H "Affinity-Version: 2026-10-28"The response is the medication with a variants array. Each entry has the same fields as the
medication and does not repeat the medication itself. variants lists only what you can order in
the mode of your key, and it has prices for the same organization. Without expand, the response
has no variants field. Any other expand value returns 400.
Read the medication
This Test medication supplies the IDs in Send an order. Catalog values can change. Use the values from your current catalog response.
{
"category": "weight_loss",
"coldChainRequired": true,
"compoundingReasons": [
"alcohol_free",
"alternate_route",
"commercial_product_discontinued",
"concentration_adjustment",
"dosage_form_unavailable",
"drug_shortage",
"flavor_adjustment",
"inactive_ingredient_sensitivity",
"inactive_ingredient_toxicity",
"modified_release",
"no_rationale_required",
"patient_cannot_use_commercial_product",
"tablet_burden"
],
"defaultDirections": null,
"fill": {
"amount": 1,
"container": "vial",
"ingredients": [
{ "amount": 60, "name": "Tirzepatide", "unit": "mg" },
{ "amount": 1, "name": "B12", "unit": "mg" }
],
"unit": "vial",
"volume": { "amount": 2, "unit": "mL" }
},
"form": "Vial",
"fullName": "Tirzepatide / B12",
"id": "med_3v9r73nmfs8bmbjb960yd2pggg",
"name": "Tirzepatide / B12",
"pharmacy": {
"id": "pharm_314xqfy8g09snbzq56416t1zb1",
"name": "PerfectRx Pharmacy"
},
"price": 11725,
"quantities": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"quantityUnit": "vial",
"requirements": [],
"restrictedStates": ["AS", "GU", "MP", "PR", "VI", "AA", "AE", "AP"],
"shippingOptions": [
{
"id": "shp_7yna882w139zfbmwzeyb16y24e",
"name": "Ground Cold Chain",
"price": 2706
}
],
"shippingSuppliesPrice": 206,
"strength": "30 mg/0.5 mg/mL"
}All prices are integers in cents. There are no fractional prices.
Fill, quantity and strength
fill is what the pharmacy dispenses for one price. For a vial product, quantityUnit is vial,
one fill is one vial, and quantity counts vials. fill.volume is what one vial holds, here 2 mL:
quantity |
Vials | Prescription price |
|---|---|---|
1 |
1 | 11725 |
6 |
6 | 70350 |
quantities lists the accepted values: 1 to 10 vials. To order 6 vials, send one prescription with
quantity: 6. Do not send 6 prescriptions. A quantity that is not in quantities, for example 12,
returns 422 invalid_order.
For other products, quantity counts quantityUnit, not fills. Tablets, capsules and creams keep
their unit. fill.amount is the number of units in one fill. The number of fills is
quantity / fill.amount, and the prescription price is price × quantity / fill.amount. Send a
quantity from quantities; each value is a whole number of fills in units. Example: PerfectRx
Anastrozole 1 mg tablets have quantityUnit: "tablet", fill.amount: 30, price: 2520 and
quantities: [30, 60, …, 300]:
quantity |
Fills | Tablets | Prescription price |
|---|---|---|---|
30 |
1 | 30 | 2520 |
60 |
2 | 60 | 5040 |
To order two fills of 30 tablets, send quantity: 60. Do not send quantity: 2: it is not in
quantities and returns 422 invalid_order. A vial is the same rule with fill.amount: 1, so
quantity counts vials.
strength is the pharmacy’s text. For a liquid, it is an amount per mL: 30 mg/0.5 mg/mL is
30 mg of tirzepatide and 0.5 mg of B12 in each mL. Do not read it as the amount in a vial.
fill.ingredients gives the total of each active ingredient in one fill: 60 mg of tirzepatide and
1 mg of B12 in one 2 mL vial. Use these totals to check a dose. Do not calculate them from strength.
fill is null when the pharmacy has not given the fill and the amount of each ingredient.
Then the prescription price is price for any accepted quantity.
| Field | Use it to |
|---|---|
fullName |
Show the name with its salt, for example Enclomiphene Citrate. It equals name when there is no salt. |
category |
Group medications. It is one of the category values above. |
requirements |
Know which optional prescription fields this pharmacy requires, for example compoundingReason. |
compoundingReasons |
Choose a compounding reason. When the list is not empty, send one of its values exactly. |
fill |
Show what one fill holds, for example a 2 mL vial with 60 mg of tirzepatide. price is for one fill. |
quantities |
Choose a quantity. When the list is not empty, send one of its values. Each value counts quantityUnit; with a fill, it is a whole number of fills. |
quantityUnit |
Show what a quantity counts. For a vial, quantity 1 is one vial; fill.volume gives its volume. |
restrictedStates |
Know where the medication cannot ship, including pharmacy coverage and medication restrictions. These are two-letter postal region codes. |
defaultDirections |
Prefill the directions. The prescriber can change them. |
shippingOptions |
Choose a shipping option. Send its id as shipping.optionId in the order. The same shipping option ID works in Test and Live. |
coldChainRequired |
Know whether the medication needs refrigerated shipping. |
shippingSuppliesPrice |
Read the supplies amount already included in each shipping option price. |
Prices for your practice
Catalog reads return prices for the current organization. Without a practiceId query parameter, the API uses the key’s organization.
A platform key therefore returns platform prices. An independent practice key returns that practice’s prices.
For each medication and shipping option, a practice pays the first available price:
- Its own price.
- Its platform’s price, when the practice has a platform.
- The default price.
A platform pays its own price when available, then the default price.
With a platform key, send practiceId in the query to quote an order for one of that platform’s child practices.
Both medication list and detail routes accept it, including expand=variants.
A foreign or archived practice returns 404. A practice key may select only itself.
API keys always act as their own organization and mode. Affinity-Organization does not override them.
curl "https://api.affinityrx.com/v1/medications?limit=10&practiceId=prac_01j9z3k4m5n6p7q8r9s0t1v2w3" \
-H "Authorization: Bearer $AFFINITY_API_KEY" \
-H "Affinity-Version: 2026-10-28"Order creation uses the target practice’s prices. The order keeps those prices when catalog prices change.
Each option price includes shippingSuppliesPrice; a mixed order pays the highest supplies price once. Example: $25.00 cold chain + $2.06 supplies = $27.06.
Do not add shippingSuppliesPrice again.
Use coldChainRequired to select refrigerated shipping.
Rules for an order
-
All prescriptions in one order go to one pharmacy. Choose medications of the same pharmacy.
-
When a field is in
requirements, send it. Otherwise the order returns422with the path of the missing field, for exampleprescriptions[0].compoundingReason. -
PerfectRx ships to all 50 states and DC. It does not ship to territories or military addresses.
-
PerfectRx also refuses a patient whose own address is in a territory other than PR, or is a military address, wherever the order ships. The order returns
422with the pathpatient.address.state. -
The order checks
restrictedStatesagainst the state of the shipping destination:- The patient’s address, the default: the path is
patient.address.state. - The practice’s address (
shipping.destination.type: "practice"): the path ispractice.address.state. - Another address (
shipping.destination.type: "address"): the path isshipping.destination.address.state.
When that state is in
restrictedStates, the order returns422with that path. - The patient’s address, the default: the path is
-
A refrigerated medication (
coldChainRequired: true) lists refrigerated shipping options only. An order with a refrigerated medication needs refrigerated shipping. Choose an option from that medication’sshippingOptions.
The requirements list can get new values when Affinity adds a pharmacy. A new value applies only
to that pharmacy’s medications. Read requirements for each medication and do not hard-code it.
Next: Send an order.