Skip to content

Quickstart

This guide takes you from zero to a paid order in the sandbox: mint a v2 API key, get a token, browse the catalog, create a customer and their protocol, send them through hosted checkout, and watch the order land.

Everything below runs against sandbox (https://api.fulfillment.sandbox.juniperhealth.com). No real payments are taken there. When you're ready for production, repeat step 1 in the production portal and swap both hostnames — see Environments.

Prerequisites: a DailyStak partner account and admin access to the partner portal.

1. Mint a v2 API key

v2 credentials are minted in the partner portal, on the API Credentials page (admin role required):

  1. Create a new credential.
  2. Select the v2 scopes: order/v2.read, order/v2.write, and order/v2.product.read.
  3. Save the client secret immediately — it is shown once and cannot be retrieved later.

Two things to get right here:

  • Explicitly pick the v2 scopes. A credential created without a scope selection is a v1 key, and v1 keys cannot call any /v2/* endpoint (see Authentication in brief).
  • A key is all-v1 or all-v2. You can't mix scopes across versions on one credential; if you also use the v1 API, keep two keys.

Export the pair for the commands below:

export JUNIPER_CLIENT_ID="1a2b3c4d5e6f7g8h9i0jklmnop"
export JUNIPER_CLIENT_SECRET="…"   # shown once at creation

2. Get an access token

Exchange the credential for a bearer token with the OAuth 2.0 client-credentials grant:

curl -s https://auth.fulfillment.sandbox.juniperhealth.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=$JUNIPER_CLIENT_ID" \
  -d "client_secret=$JUNIPER_CLIENT_SECRET"
{
  "access_token": "eyJraWQiOiJ...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Tokens last one hour and carry the scopes your credential was minted with. Cache the token and reuse it until shortly before expires_in elapses — don't request a fresh token per API call.

export TOKEN="eyJraWQiOiJ..."
export API="https://api.fulfillment.sandbox.juniperhealth.com"

Every API call below sends it as Authorization: Bearer $TOKEN.

3. List products

Browse the catalog to find product IDs for the regimen. This endpoint uses your order/v2.product.read scope:

curl -s "$API/v2/products?limit=2" \
  -H "Authorization: Bearer $TOKEN"

Abridged response — note product IDs are integers, and pricing comes back as decimal strings with your partner-specific breakdown:

{
  "products": [
    {
      "id": 77123129891,
      "name": "Elite EPA",
      "description": "High-potency omega-3 fish oil",
      "packDescription": "Whole Body Wellness",
      "external": false,
      "form": "Softgel",
      "status": "ACTIVE",
      "pricing": {
        "retailPrice": "0.53",
        "partnerPrice": "0.32",
        "customerPrice": "0.38",
        "customerDiscount": 28,
        "partnerDiscount": 40,
        "commission": "0.06"
      },
      "recommendedUsage": { "morning": 1, "afternoon": 0, "evening": 1, "sleep": 0 },
      "createdAt": "2026-01-14T17:02:56+00:00",
      "updatedAt": "2026-06-30T09:12:04+00:00"
    }
  ],
  "cursor": "eyJwayI6ICJQUk9EVUNUIzc3MTIzMTI5ODkxIn0=",
  "totalHits": null
}

Keep paging until cursor is null. You can also pass search=omega for fuzzy catalog search, or customerId to price for a specific customer. Details in Products.

4. Create a customer

Customers are your patients/clients on the platform. sourceId is your own system's ID for them — it's unique per partner and doubles as your idempotency key on retries:

curl -s -X POST "$API/v2/customers" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "sourceId": "crm-1042",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "phone": "3125550188",
    "shippingAddress": {
      "line1": "123 Main St",
      "city": "Austin",
      "state": "TX",
      "zipCode": "78701"
    }
  }'
{
  "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  "partnerId": "2OKpLhTlpCrQHabz5X9qRbH4abc",
  "sourceId": "crm-1042",
  "firstName": "Jane",
  "lastName": "Doe",
  "fullName": "Jane Doe",
  "email": "jane.doe@example.com",
  "phone": "3125550188",
  "shippingAddress": {
    "line1": "123 Main St",
    "line2": null,
    "city": "Austin",
    "state": "TX",
    "zipCode": "78701"
  },
  "billingAddress": null,
  "discountOverride": null,
  "partnerConsent": false,
  "createdAt": "2026-07-25T14:02:11+00:00",
  "updatedAt": "2026-07-25T14:02:11+00:00"
}

A duplicate sourceId or email returns 409. This create emits the customer.created webhook event. Save the id — protocols reference it:

export CUSTOMER_ID="2vTgd30JFgnwLrgb383PcV9Rken"

5. Create a protocol

A protocol is the customer's regimen: which products, how many, and when in the day. It deliberately contains no shipping or billing details — those are chosen at checkout. For pack (non-external) products, administration is required and its dayparts must sum to quantity:

curl -s -X POST "$API/v2/protocols" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "'"$CUSTOMER_ID"'",
    "lineItems": [
      {
        "productId": 77123129891,
        "quantity": 2,
        "administration": { "morning": 1, "evening": 1 }
      }
    ]
  }'

Abridged response:

{
  "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  "status": "ACTIVE",
  "orderType": null,
  "customer": {
    "id": "2vTgd30JFgnwLrgb383PcV9Rken",
    "sourceId": "crm-1042",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com"
  },
  "lineItems": [
    {
      "productId": 77123129891,
      "name": "Elite EPA",
      "quantity": 2,
      "administration": { "morning": 1, "afternoon": 0, "evening": 1, "sleep": 0 }
    }
  ],
  "hasExternal": false,
  "createdAt": "2026-07-25T14:05:40+00:00",
  "updatedAt": "2026-07-25T14:05:40+00:00"
}

A customer may hold several active protocols at once, and this emits protocol.created. See Protocols.

export PROTOCOL_ID="2vSGym0bH8qVEwCIGlyFoRgJq1A"

6. Create a checkout session

A checkout session is a hosted, tokenized payment page, valid for 24 hours. Created from a protocolId, it pulls the line items from the regimen and the customer's identity and shipping address from their record. mode: "subscription" means completing checkout also creates a subscription with the card saved for renewals (daysSupply is required in that mode):

curl -s -X POST "$API/v2/checkout/sessions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "protocolId": "'"$PROTOCOL_ID"'",
    "mode": "subscription",
    "daysSupply": 30,
    "successUrl": "https://partner.example.com/thanks",
    "source": "api"
  }'

Abridged response:

{
  "sessionId": "2vUhN8mPzQxWvYtR5cJkLbD3eFg",
  "status": "pending",
  "mode": "subscription",
  "customerEmail": "jane.doe@example.com",
  "customerFirstName": "Jane",
  "customerLastName": "Doe",
  "daysSupply": 30,
  "lineItems": [
    {
      "id": "2vUhN9aBcDeFgHiJkLmNoPqRsTu",
      "productId": "77123129891",
      "name": "Elite EPA",
      "quantity": 2,
      "unitPrice": "0.38"
    }
  ],
  "subtotal": "22.80",
  "totalAmount": "24.68",
  "currency": "usd",
  "shippingMethod": null,
  "checkoutUrl": "https://api.fulfillment.sandbox.juniperhealth.com/checkout/9f4c1e8a7b2d4c0a9e6f3b1d8c5a2e70",
  "orderId": null,
  "subscriptionId": null,
  "expiresAt": "2026-07-26T14:09:22+00:00",
  "createdAt": "2026-07-25T14:09:22+00:00",
  "updatedAt": "2026-07-25T14:09:22+00:00"
}

Send your customer to checkoutUrl (email, SMS, or a redirect from your app). The quoted amounts already include tax and all discounts, but not shipping — totalAmount updates when the customer picks a shipping method on the page, and the charge equals that final amount. Treat the URL as sensitive: the embedded token is the only credential for the payment page.

Note productId is a string on checkout line items, unlike the integer catalog ID. Also, creating a new session for the same protocol cancels any older pending ones — at most one session per protocol is active. See Checkout.

7. Watch for completion

Checkout completes asynchronously: after the customer pays on the hosted page, the order (and the subscription, in subscription mode) are created within seconds. You have two ways to observe it.

Sandbox note: payment-settlement events do not flow end to end in sandbox, so this completion step may not fire there. Everything up to and including creating the session works in sandbox; verify completion with a low-value transaction in production.

Preferred — the order.created webhook. If you've configured a webhook endpoint (see Webhooks), you'll receive:

{
  "id": "evt_2vUiQ3rTsUvWxYzA1bC2dE3fG4h",
  "type": "order.created",
  "created": 1784991012,
  "data": {
    "orderId": "2vUiQ2kLmNpQrStUvWxYzAbCdEf",
    "status": "NEW",
    "fulfillmentInfo": null,
    "createdAt": "2026-07-25T14:50:12+00:00",
    "updatedAt": "2026-07-25T14:50:12+00:00",
    "checkoutSessionId": "2vUhN8mPzQxWvYtR5cJkLbD3eFg",
    "metadata": null
  }
}

checkoutSessionId is the session you created in step 6, so you can match the order to it without polling; metadata echoes whatever you sent in the session's metadata (none here).

Alternative — poll the session:

curl -s "$API/v2/checkout/sessions/2vUhN8mPzQxWvYtR5cJkLbD3eFg" \
  -H "Authorization: Bearer $TOKEN"

On completion, status becomes "completed" and orderId (plus subscriptionId in subscription mode) populate. Poll at a gentle interval (a few seconds); don't hammer the endpoint.

One quirk to know: checkout-created subscriptions do not emit subscription.created — the order.created event is your completion signal for both.

8. Fetch the order

curl -s "$API/v2/orders/2vUiQ2kLmNpQrStUvWxYzAbCdEf" \
  -H "Authorization: Bearer $TOKEN"

Abridged response:

{
  "id": "2vUiQ2kLmNpQrStUvWxYzAbCdEf",
  "customerId": "2vUiP9hJkLmNoPqRsTuVwXyZaBc",
  "status": "NEW",
  "orderType": "SUBSCRIPTION",
  "daysSupply": 30,
  "isFirstOrder": true,
  "shippingMethod": "STANDARD",
  "customer": {
    "sourceId": "crm-1042",
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane.doe@example.com",
    "address": { "line1": "123 Main St", "line2": null, "city": "Austin", "state": "TX", "zipCode": "78701" }
  },
  "customerTotal": "24.68",
  "partnerCommission": "3.60",
  "fulfillmentInfo": null,
  "createdAt": "2026-07-25T14:50:12+00:00",
  "updatedAt": "2026-07-25T14:50:12+00:00"
}

The pricing on the order is frozen at creation — later catalog changes never reprice it. From here the order advances through fulfillment on its own (NEW → PROCESSING → DISPENSING → FILLED → SHIPPED → DELIVERED), and each transition emits an order.* webhook event with tracking details in fulfillmentInfo once shipped. See Orders for the full lifecycle.

Where to go next

  • Get webhooks set up properly — signature verification, retries, and the full event catalog: Webhooks
  • Recurring billing — pause/resume/cancel, renewal notices, and partner-managed renewals: Subscriptions
  • Changing a live regimen — the amendment flow and customer consent: Protocols & Amendments
  • Refunds — reading an order's refunds and following them to completion (heads-up: amounts are integer cents): Refunds
  • Your account — branding, email toggles, shipping methods, custom checkout domains: Partner Settings & Custom Domains