Use CompoundingReason from the TypeScript SDK. Send Affinity categories through clinical.compoundingReason on each prescription.
Affinity translates supported categories to pharmacy codes. Your integration does not need vendor enums or payload formatting rules.
Keep SDK calls on your server. Use a Test API key and synthetic patient data while developing this workflow.
See Compounding reasons for the clinical meaning and pharmacy requirements.
Load the medication requirements
const options = await affinity.catalog.prescribingOptions.get(medicationId, {
practiceId,
});
const reasons = options.compoundingReason;The API returns the requirements for the selected medication. Do not assume every pharmacy accepts every SDK category.
See the requirements fields for the response contract.
Use choices to populate your reason selector. Show the selected choice’s context prompt when available.
If choices is empty and context supports text, show an explanation field without a selector.
If both are unavailable, hide the reason fields.
Load options again when the medication changes. Supply expectedRevision during preview to detect changed requirements or defaults.
Collect the clinician’s selection
The SDK exports a constant object and a matching string-union type:
import { CompoundingReason } from "@affinity-health/sdk";
const selectedCategory: CompoundingReason = CompoundingReason.ConcentrationAdjustment;
const equivalentCategory: CompoundingReason = "concentration_adjustment";Both forms support autocomplete. Values such as PerfectRx’s CONC_ADJUST are vendor codes, not valid Affinity categories.
The clinician must select the applicable reason. Medication defaults do not establish the patient’s clinical need. Do not infer a diagnosis or patient-specific explanation from the selected medication.
Send the reason
For a category-only pharmacy such as PerfectRx, omit context:
compoundingReason: {
category: CompoundingReason.ConcentrationAdjustment,
}When the medication requires patient-specific context, send the clinician’s explanation:
compoundingReason: {
category: CompoundingReason.ConcentrationAdjustment,
context: clinicianEnteredExplanation,
}For a text-only pharmacy, omit the category:
compoundingReason: {
context: clinicianEnteredExplanation,
}Omit the entire compoundingReason object when an optional reason is unused.
An empty string does not satisfy required context. A category alone does not replace a patient-specific explanation.
PerfectRx receives its mapped enum. Pharmetika-backed pharmacies receive their mapped category and the required context. Text-only integrations receive the supplied explanation. Affinity does not invent patient facts or a clinical rationale.
Preview and create an unsigned order
The following variables come from your application’s completed prescription form.
prescription contains reviewed directions, numeric quantity, days supply, refills, dispensing details, and any other clinical information.
selectedCategory comes from the accepted choices; leave it undefined for a text-only pharmacy.
import { Affinity, type CompoundingReason, type OrderCreateParams } from "@affinity-health/sdk";
const affinity = new Affinity(process.env.AFFINITY_API_KEY!);
type Prescription = OrderCreateParams["prescriptions"][number] & { quantity: number };
declare const prescription: Prescription;
declare const practiceId: string;
declare const patientId: string;
declare const selectedCategory: CompoundingReason | undefined;
declare const clinicianEnteredExplanation: string;
declare const persistedOrderCreationKey: string;
const options = await affinity.catalog.prescribingOptions.get(prescription.medicationId, {
practiceId,
});
const context = clinicianEnteredExplanation.trim();
const compoundingReason =
selectedCategory || context
? {
...(selectedCategory ? { category: selectedCategory } : {}),
...(context ? { context } : {}),
}
: undefined;
const preview = await affinity.forPractice(practiceId).orders.preview({
patientId,
prescriptions: [
{
medicationId: prescription.medicationId,
expectedRevision: options.revision,
overrides: {
sig: { format: "free_text", text: prescription.directions },
quantity: { value: prescription.quantity, unit: prescription.quantityUnit },
daysSupply: prescription.daysSupply,
refills: prescription.refills,
dispensing: prescription.dispensing,
clinical: { ...prescription.clinical, compoundingReason },
},
},
],
});
if (preview.status === "incomplete") {
// Display preview.issues beside the affected fields. Nothing has been created.
} else {
// After the clinician reviews these exact values:
const { practiceId: resolvedPracticeId, ...params } = preview.orderInput;
const order = await affinity.forPractice(resolvedPracticeId).orders.create(params, {
idempotencyKey: persistedOrderCreationKey,
});
}Reuse the same idempotency key when retrying creation across calls or processes. Creation produces an unsigned order. Signing requires the clinician’s attestation and the exact prescription versions. See Prescription defaults and previews for shipping selection and default resolution.
Handle changed or incomplete input
Preview returns field issues when a required category or explanation is missing, or the medication does not accept the category. Display those issues and let the clinician correct the input. Creation and signing recheck current requirements.
Do not automatically replace an unsupported category with OtherPatientSpecificNeed or NoRationaleRequired.
After a medication change, confirm the accepted choices again. Reusing a previous prescription does not establish a current clinical rationale.
Available SDK categories
The SDK accepts either the constant or its exact string value. Both support autocomplete.
Offer only the subset returned in the medication’s choices.
See reason labels and API values for the general reference.
| SDK constant | API value |
|---|---|
CompoundingReason.AlcoholFree |
alcohol_free |
CompoundingReason.DrugShortage |
drug_shortage |
CompoundingReason.CommercialProductDiscontinued |
commercial_product_discontinued |
CompoundingReason.ModifiedRelease |
modified_release |
CompoundingReason.InactiveIngredientSensitivity |
inactive_ingredient_sensitivity |
CompoundingReason.InactiveIngredientToxicity |
inactive_ingredient_toxicity |
CompoundingReason.ConcentrationAdjustment |
concentration_adjustment |
CompoundingReason.AlternateRoute |
alternate_route |
CompoundingReason.DosageFormUnavailable |
dosage_form_unavailable |
CompoundingReason.FlavorAdjustment |
flavor_adjustment |
CompoundingReason.TabletBurden |
tablet_burden |
CompoundingReason.PatientCannotUseCommercialProduct |
patient_cannot_use_commercial_product |
CompoundingReason.NoApprovedProductAvailable |
no_approved_product_available |
CompoundingReason.NoRationaleRequired |
no_rationale_required |
CompoundingReason.OtherPatientSpecificNeed |
other_patient_specific_need |