Developer Docs · v1

Build on ScriptRun

Push orders into ScriptRun from your pharmacy software. Receive real-time delivery events on your server. Every endpoint is authenticated, idempotent where it matters, and documented with a working curl example.

Quick start

Push your first order in under five minutes.

  1. Sign in, open Settings → Integrations, and create an API key. Copy it — it's shown once.
  2. POST a single order to /api/webhook/orders with the key in the X-API-Key header.
  3. Watch it appear in your Dashboard in real time.
bash curl
curl -X POST https://scriptrun.app/api/webhook/orders \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "orders": [{ "patient_name": "Maria Rodriguez", "patient_address": "123 Main St, Miami, FL 33178", "patient_phone": "+13055551234", "rx_number": "RX-8872341", "copay_amount": 12.50, "priority": "routine", "requires_signature": true }] }'

Response 200 OK:

json
{ "created": 1, "package_ids": ["a3e9f1..."], "warnings": [] }

Authentication

All API requests are authenticated with an API key passed in the X-API-Keyheader. Keys are scoped to a single tenant (pharmacy or dispatcher account) and inherit that tenant's plan limits and feature flags.

  • Keys are stored hashed (SHA-256); the plaintext is never retrievable.
  • Revoke a key anytime from the Integrations page. Revocation is immediate.
  • Prefix sk_live_ denotes a production key. Prefix sk_test_ denotes a test/sandbox key.

Push orders

POST /api/webhook/orders accepts a batch of up to 100 orders in a single request. Each order is validated individually — partial success is possible and is reported in the response.

Request schema

patient_name
string
Required
Full name as it should appear to the driver.
patient_address
string
Required
Single-line mailing address. Geocoded automatically.
patient_phone
string
Optional
E.164 format. Required if SMS notifications are enabled.
rx_number
string
Optional
Prescription number. Stored; never shown to driver.
medications
string[]
Optional
Array of medication names. Never shown to driver.
copay_amount
number
Optional
In dollars. Collected at door if present.
priority
enum
Optional
"routine" | "urgent" | "stat". Default "routine".
delivery_date
string
Optional
YYYY-MM-DD. Default today.
requires_signature
boolean
Optional
Default false.
cold_chain
boolean
Optional
Triggers insulated-bag workflow.
controlled_substance
boolean
Optional
Triggers DEA-schedule workflow.
dea_schedule
enum
Optional
"II" | "III" | "IV" | "V" if controlled_substance=true.
external_patient_id
string
Optional
Your PMS patient ID. Used for recurring-delivery matching.
pharmacy_id
uuid
Optional
For multi-location accounts.
notes
string
Optional
Free-form dispatcher notes.
barcode
string
Optional
Package barcode, if pre-printed.

Multiple orders in one call:

json
POST /api/webhook/orders X-API-Key: sk_live_... { "orders": [ { "patient_name": "A. Smith", "patient_address": "1 Ocean Dr, Miami Beach FL" }, { "patient_name": "B. Jones", "patient_address": "200 Biscayne, Miami FL", "priority": "urgent" } ] }

Outbound webhooks

Subscribe to real-time events on delivery lifecycle. Configure destination URL and events from Settings → Integrations → Webhooks. Requires a Pro plan or higher.

  • Each webhook endpoint gets a signing secret — returned once at creation.
  • Deliveries use HTTPS POST with a JSON body.
  • Non-2xx responses are retried with exponential backoff for up to 24 hours.
  • Delivery attempts are logged in webhook_logs for audit.

Event types

delivery.completed
Package marked delivered with photo + signature.
delivery.failed
Driver marked failed (wrong address, refused, no answer, etc.).
package.created
New order ingested via API, CSV, or manual entry.
route.completed
Driver finished all stops on an assigned route.
driver.status_changed
Driver went online/offline or shift started/ended.
*
Wildcard — subscribe to every event type.

Example payload: delivery.completed

json
POST https://your-server.example.com/webhooks/scriptrun Content-Type: application/json X-ScriptRun-Signature: t=1713547200,v1=5a3e... X-ScriptRun-Event: delivery.completed { "id": "evt_9f2c...", "event": "delivery.completed", "created_at": "2026-04-19T18:42:10Z", "data": { "package_id": "a3e9f1...", "external_patient_id": "PMS-9081", "rx_number": "RX-8872341", "driver_id": "drv_b12c...", "delivered_at": "2026-04-19T18:41:57Z", "proof": { "photo_url": "https://storage.scriptrun.app/...", "signature_url": "https://storage.scriptrun.app/...", "lat": 25.8612, "lng": -80.2983 }, "copay_collected": 12.50, "copay_method": "card" } }

Signing & verification

Every outbound webhook is signed with HMAC-SHA256 using the secret you were given at creation. Verify before trusting the payload.

typescript
import crypto from 'node:crypto' function verify(rawBody: string, header: string, secret: string) { // header format: "t=<unix_ts>,v1=<sig>" const parts = Object.fromEntries(header.split(',').map(p => p.split('='))) const signed = `${parts.t}.${rawBody}` const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex') if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))) { throw new Error('Invalid signature') } // reject payloads older than 5 minutes if (Math.abs(Date.now()/1000 - Number(parts.t)) > 300) { throw new Error('Stale webhook') } }

CSV import

Don't have engineering resources? Upload a CSV from the Packages page. The dispatcher can drag and drop a spreadsheet export from your PMS. Here's the expected format.

csv
patient_name,patient_address,patient_phone,rx_number,copay_amount,priority,delivery_date,notes Maria Rodriguez,"123 Main St, Miami, FL 33178",+13055551234,RX-8872341,12.50,routine,2026-04-20,"Leave with doorman" Alex Kim,"450 Collins Ave, Miami Beach, FL 33139",+13055559876,RX-8872589,0.00,urgent,2026-04-20,

Columns map to the same field names as the JSON API. Unknown columns are ignored. Empty cells become null. Rows with validation errors are reported individually; the good rows still import.

Shopify

Selling pharmacy goods on Shopify? Install our app from Settings → Integrations → Shopify. We listen for orders/create and orders/paid webhooks and create packages automatically. Fulfillment and refund events flow back to Shopify on delivery.completed / delivery.failed.

Rate limits

Limits are per API key. If you need higher throughput, email us.

  • Orders ingest: 600 requests/minute · 100 orders/batch · 10,000 orders/hour
  • Read endpoints: 60 requests/minute
  • OCR / label scan: 30 requests/minute

Hitting a limit returns 429 Too Many Requests with a Retry-After header. Back off with jitter.

Errors

Responses follow standard HTTP status codes.

  • 200 OK — success
  • 400 Bad Request — validation failure. Response body has error and details array.
  • 401 Unauthorized — missing or invalid X-API-Key.
  • 402 Payment Required — plan limit exceeded and no overage billing enabled.
  • 403 Forbidden — feature not available on current plan.
  • 429 Too Many Requests — rate limited.
  • 5xx — our fault. Retry with backoff.

Support

Engineering questions: developers@scriptrun.app. Typical response within one business day.

Reporting a security issue: security@scriptrun.app. See /security.

Contractual questions (BAA, MSA): /baa and /terms.