Partner Order Service API v2 (2.2.0)

Download OpenAPI specification:

DailyStak Engineering Team: engineering@juniperhealth.com License: Proprietary Terms of Service

Partner API v2 for managing orders, subscriptions, customers, checkout, and fulfillment.

Authentication

This API uses the OAuth 2.0 Client Credentials flow with a v2 API key minted in the partner portal. v2 credentials are separate from v1: a v1 key cannot call any /v2/* endpoint and a v2 key cannot call /v1/*.

Scopes. Read endpoints require order/v2.read (product catalog endpoints: order/v2.product.read). Write endpoints (POST, PUT, PATCH, DELETE) require both the read scope and its .write counterpart on the token. Each operation below lists its exact scopes.

Token Endpoints:

  • Sandbox: https://auth.fulfillment.sandbox.juniperhealth.com/oauth2/token
  • Production: https://auth.fulfillment.juniperhealth.com/oauth2/token

Request:

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Include the token in API requests using the Authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN

A missing or unparseable token returns 401; a token that lacks a required scope, is expired, or belongs to a suspended partner returns 403.

Conventions

  • JSON is camelCase on the wire, for requests and responses alike, with no exceptions — including query parameters, the amendment and scheduled-change payload objects, and the order shipping-address update body. In amendment and scheduled-change payloads the older snake_case spellings (product_id, take_with) are still accepted on input; responses and webhooks always use productId and takeWith.
  • Timestamps are ISO 8601 UTC. Null fields are present in responses rather than omitted.
  • Money: refunds use integer cents; checkout and pricing amounts are decimal strings.
  • Errors return {"message": "...", "code": "..."} bodies, plus a details object when the refusal names specifics. Branch on code, never on message — messages are reworded without notice, and details is omitted rather than sent as null. Validation failures are 422 with code: VALIDATION_FAILED; field locations and rejected values are never sent back. Two bodies differ by design: partner-hosted (Elements) checkout refusals carry the code in error ({"message", "error"}), and a 401 from the API gateway itself carries message only.
  • Pagination is cursor-based where offered: pass the previous response's cursor and keep paging until the cursor comes back null (the field is cursor on most lists, nextCursor on checkout sessions, and customer lists also carry hasMore). Filtered pages can be short or empty while more results remain.
  • Tenancy: every resource is scoped to your partner account; another partner's resource is indistinguishable from a missing one (404).

Orders

Create, retrieve, and manage customer orders

Create a New Order

Submit a customer order with product line items and shipping details. Pricing is computed and frozen at creation (the settlement snapshot); later catalog or discount changes never affect an existing order.

shippingMethod must match one of your configured shipping methods — otherwise the request fails with a 400 whose body lists availableShippingMethods.

There is no idempotency key: retrying a successful create makes a duplicate order (never a duplicate customer). Creating an order emits the order.created webhook event and — unlike v1 — sends your customer a branded order confirmation email (toggleable in partner settings).

The order is resolved to a Customer record in your account, first step that yields one wins: a top-level customerId (404 and no order if it does not resolve), else customer.sourceId, else customer.email — accepted only when exactly one active record matches, it is neither a caregiver-managed dependent nor a caregiver with dependents, and the names agree — else a record is created from the snapshot when it carries an email. A created record has no sourceId. The linked id is echoed as customerId; it is null only when no usable email was sent or the email match was refused. Only a customerId or sourceId match applies the customer's discountOverride and stamps isFirstOrder; an email match or a created record prices at your partner rate.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required

Order details to be created in the system

required
object

Customer information for the order

sourceId
required
string

Unique customer identifier in your system

firstName
required
string

Customer's first name

lastName
required
string

Customer's last name

required
object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

email
string <email>

Customer's email address

phone
string

Customer's phone number

daysSupply
required
integer [ 14 .. 120 ]

Number of days the order is intended to supply (any value 14–120)

required
Array of objects (LineItemRequest) [ 1 .. 99 ] items

Products included in the order

Array ([ 1 .. 99 ] items)
productId
required
integer

Unique identifier of the product

quantity
required
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null <= 500 characters

Provider instructions for this line

templateId
string or null

Protocol-template provenance identifier

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method for the order. Must also be one of the methods configured for your account, or the request fails with 400 listing availableShippingMethods.

orderType
required
string
Enum: "ONE_TIME" "SUBSCRIPTION"

Type of order

customerId
string or null

Existing Customer id to link this order to. Takes precedence over customer.sourceId and customer.email; 404 if it does not resolve in your account. The embedded customer is still required and supplies the delivery address.

Responses

Response Schema: application/json
payer
string or null
Enum: "customer" "practice"

Who was charged for this order, frozen when it was created. customer — their own card. practice — the practice's saved card at wholesale, with no commission (a practice-paid customer; see Customer.payer). Null on orders from before the field existed, which were customer-paid.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
id
string

Unique order identifier

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Current status. AWAITING_SHIPMENT is deprecated (never written); DELIVERED is set from carrier tracking; EXCEPTION indicates a fulfillment problem.

isFirstOrder
boolean

Whether this is the linked customer's first order with your account. Computed at creation. False when the order is not linked, and false for a customer the order itself created or matched by email — only a customer referenced by customerId or sourceId is stamped.

customerId
string or null

Internal id of the Customer record this order is linked to; null when the order could not be linked.

subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. A customer with more than one subscription cannot be attributed from orderType alone — this is the id to key off.

protocolId
string or null

The protocol this order was built from; null for API orders. It survives a subscription being replaced, so it is the stabler key for grouping a customer's recurring orders.

object or null

The card that paid for this order, recorded once when the payment settled; reading the order never asks the payment provider. null when no card is recorded: orders paid before this field existed, payments that were not by card, orders with no customer payment (such as those created with POST /v2/orders), and the rare payment whose card the provider could not report at the time. Checkout and renewal orders gain it moments after they are created, so a read prompted by order.created can see null; the write that adds the card sends another order.* event for the order.

brand
string or null

Card brand

last4
string or null

Last four digits

expMonth
integer or null

Expiry month, 1-12

expYear
integer or null

Expiry year, four digits

discount
string
Deprecated

Vestigial; always "0.00"

daysSupply
integer

Number of days the order supplies

orderType
string
Enum: "ONE_TIME" "SUBSCRIPTION"
shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

Array
checkpointTime
string <date-time>

Timestamp of the checkpoint event

location
string

Location of the shipment

message
string

Status message from the carrier

tag
string

Status tag

Array of objects (LineItem)

Products included in the order

Array
id
string

Order line-item identifier. Pass it as lineItemId when creating a line-item refund.

productId
integer

Unique identifier of the product

name
string

Product name

quantity
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

price
string

Per-unit customer price

discountedPrice
string
Deprecated

Vestigial; always "0.00"

totalPrice
string

Customer price × quantity (× days supply for pack items; external items are not multiplied by days supply)

totalRetailPrice
string

Same multiplication rule at retail price

totalPartnerPrice
string

Same multiplication rule at partner (wholesale) price

totalCustomerPrice
string

Equals totalPrice

external
boolean

Whether this is an external product (powder, liquid) shipped as-is

imageUrl
string or null <uri>

Product image URL (currently always null)

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null
templateId
string or null
object

Embedded order customer snapshot (not the full Customer resource)

sourceId
string
firstName
string
lastName
string
email
string or null
phone
string or null

Digit-normalized

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

retailSubtotal
string

Sum of retail (MSRP) line totals

partnerSubtotal
string

Wholesale subtotal the partner pays

customerSubtotal
string

Customer subtotal

totalDiscount
string

Order-level customer discount amount

totalFees
string

Platform handling fees on external products. Under Juniper-managed billing these reduce commission and are not charged to the customer; under partner-managed billing they are part of the total.

totalShipping
string

Shipping charged

totalTax
string

Tax charged ("0.00" for API-created orders)

totalVolumeDiscount
string

Volume discount applied automatically from your configured tiers

customerTotal
string

Final total charged to the customer (frozen at creation)

partnerCommission
string

Frozen commission; may be negative

createdAt
string <date-time>
updatedAt
string <date-time>

Request samples

Content type
application/json
{
  • "customer": {
    },
  • "customerId": "string",
  • "daysSupply": 30,
  • "lineItems": [
    ],
  • "shippingMethod": "STANDARD",
  • "orderType": "ONE_TIME"
}

Response samples

Content type
application/json
{
  • "payer": "customer",
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "status": "NEW",
  • "isFirstOrder": true,
  • "customerId": "string",
  • "subscriptionId": null,
  • "protocolId": null,
  • "paymentMethod": {
    },
  • "discount": "string",
  • "daysSupply": 30,
  • "orderType": "SUBSCRIPTION",
  • "shippingMethod": "STANDARD",
  • "fulfillmentInfo": {
    },
  • "lineItems": [
    ],
  • "customer": {
    },
  • "retailSubtotal": "60.00",
  • "partnerSubtotal": "36.00",
  • "customerSubtotal": "30.00",
  • "totalDiscount": "27.00",
  • "totalFees": "5.00",
  • "totalShipping": "9.99",
  • "totalTax": "0.00",
  • "totalVolumeDiscount": "12.10",
  • "customerTotal": "36.99",
  • "partnerCommission": "5.00",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Retrieve Orders

Retrieve a paginated list of orders, or search them.

limit values outside 1–100 are clamped, not rejected. With search, the status and direction parameters are ignored, and matching is fuzzy over customer name, order ID, customer source ID, and product names; search is served from a secondary index that is populated asynchronously (eventually consistent). Cursors from search and non-search responses are not interchangeable. Filtering by status returns results in index order rather than newest-first.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 10

Maximum number of results per page

cursor
string

Pagination cursor for retrieving next/previous page

direction
string
Enum: "next" "previous"

Direction for pagination (next or previous)

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Filter orders by status. AWAITING_SHIPMENT is deprecated (accepted but no longer written).

search
string
Example: search=John Doe

Fuzzy search over customer name, order ID, customer source ID, and product names

Responses

Response Schema: application/json
Array of objects (Order)
Array
payer
string or null
Enum: "customer" "practice"

Who was charged for this order, frozen when it was created. customer — their own card. practice — the practice's saved card at wholesale, with no commission (a practice-paid customer; see Customer.payer). Null on orders from before the field existed, which were customer-paid.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

id
string

Unique order identifier

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Current status. AWAITING_SHIPMENT is deprecated (never written); DELIVERED is set from carrier tracking; EXCEPTION indicates a fulfillment problem.

isFirstOrder
boolean

Whether this is the linked customer's first order with your account. Computed at creation. False when the order is not linked, and false for a customer the order itself created or matched by email — only a customer referenced by customerId or sourceId is stamped.

customerId
string or null

Internal id of the Customer record this order is linked to; null when the order could not be linked.

subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. A customer with more than one subscription cannot be attributed from orderType alone — this is the id to key off.

protocolId
string or null

The protocol this order was built from; null for API orders. It survives a subscription being replaced, so it is the stabler key for grouping a customer's recurring orders.

object or null

The card that paid for this order, recorded once when the payment settled; reading the order never asks the payment provider. null when no card is recorded: orders paid before this field existed, payments that were not by card, orders with no customer payment (such as those created with POST /v2/orders), and the rare payment whose card the provider could not report at the time. Checkout and renewal orders gain it moments after they are created, so a read prompted by order.created can see null; the write that adds the card sends another order.* event for the order.

discount
string
Deprecated

Vestigial; always "0.00"

daysSupply
integer

Number of days the order supplies

orderType
string
Enum: "ONE_TIME" "SUBSCRIPTION"
shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

Array of objects (LineItem)

Products included in the order

object

Embedded order customer snapshot (not the full Customer resource)

retailSubtotal
string

Sum of retail (MSRP) line totals

partnerSubtotal
string

Wholesale subtotal the partner pays

customerSubtotal
string

Customer subtotal

totalDiscount
string

Order-level customer discount amount

totalFees
string

Platform handling fees on external products. Under Juniper-managed billing these reduce commission and are not charged to the customer; under partner-managed billing they are part of the total.

totalShipping
string

Shipping charged

totalTax
string

Tax charged ("0.00" for API-created orders)

totalVolumeDiscount
string

Volume discount applied automatically from your configured tiers

customerTotal
string

Final total charged to the customer (frozen at creation)

partnerCommission
string

Frozen commission; may be negative

createdAt
string <date-time>
updatedAt
string <date-time>
cursor
string or null (Cursor)

Pagination cursor for retrieving next/previous page; null when there are no more pages

Response samples

Content type
application/json
{
  • "orders": [
    ],
  • "cursor": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Retrieve Order Details

Get detailed information for a specific order

Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string
Example: 2vSGym0bH8qVEwCIGlyFoRgJq1A

Responses

Response Schema: application/json
payer
string or null
Enum: "customer" "practice"

Who was charged for this order, frozen when it was created. customer — their own card. practice — the practice's saved card at wholesale, with no commission (a practice-paid customer; see Customer.payer). Null on orders from before the field existed, which were customer-paid.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
id
string

Unique order identifier

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Current status. AWAITING_SHIPMENT is deprecated (never written); DELIVERED is set from carrier tracking; EXCEPTION indicates a fulfillment problem.

isFirstOrder
boolean

Whether this is the linked customer's first order with your account. Computed at creation. False when the order is not linked, and false for a customer the order itself created or matched by email — only a customer referenced by customerId or sourceId is stamped.

customerId
string or null

Internal id of the Customer record this order is linked to; null when the order could not be linked.

subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. A customer with more than one subscription cannot be attributed from orderType alone — this is the id to key off.

protocolId
string or null

The protocol this order was built from; null for API orders. It survives a subscription being replaced, so it is the stabler key for grouping a customer's recurring orders.

object or null

The card that paid for this order, recorded once when the payment settled; reading the order never asks the payment provider. null when no card is recorded: orders paid before this field existed, payments that were not by card, orders with no customer payment (such as those created with POST /v2/orders), and the rare payment whose card the provider could not report at the time. Checkout and renewal orders gain it moments after they are created, so a read prompted by order.created can see null; the write that adds the card sends another order.* event for the order.

brand
string or null

Card brand

last4
string or null

Last four digits

expMonth
integer or null

Expiry month, 1-12

expYear
integer or null

Expiry year, four digits

discount
string
Deprecated

Vestigial; always "0.00"

daysSupply
integer

Number of days the order supplies

orderType
string
Enum: "ONE_TIME" "SUBSCRIPTION"
shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

Array
checkpointTime
string <date-time>

Timestamp of the checkpoint event

location
string

Location of the shipment

message
string

Status message from the carrier

tag
string

Status tag

Array of objects (LineItem)

Products included in the order

Array
id
string

Order line-item identifier. Pass it as lineItemId when creating a line-item refund.

productId
integer

Unique identifier of the product

name
string

Product name

quantity
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

price
string

Per-unit customer price

discountedPrice
string
Deprecated

Vestigial; always "0.00"

totalPrice
string

Customer price × quantity (× days supply for pack items; external items are not multiplied by days supply)

totalRetailPrice
string

Same multiplication rule at retail price

totalPartnerPrice
string

Same multiplication rule at partner (wholesale) price

totalCustomerPrice
string

Equals totalPrice

external
boolean

Whether this is an external product (powder, liquid) shipped as-is

imageUrl
string or null <uri>

Product image URL (currently always null)

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null
templateId
string or null
object

Embedded order customer snapshot (not the full Customer resource)

sourceId
string
firstName
string
lastName
string
email
string or null
phone
string or null

Digit-normalized

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

retailSubtotal
string

Sum of retail (MSRP) line totals

partnerSubtotal
string

Wholesale subtotal the partner pays

customerSubtotal
string

Customer subtotal

totalDiscount
string

Order-level customer discount amount

totalFees
string

Platform handling fees on external products. Under Juniper-managed billing these reduce commission and are not charged to the customer; under partner-managed billing they are part of the total.

totalShipping
string

Shipping charged

totalTax
string

Tax charged ("0.00" for API-created orders)

totalVolumeDiscount
string

Volume discount applied automatically from your configured tiers

customerTotal
string

Final total charged to the customer (frozen at creation)

partnerCommission
string

Frozen commission; may be negative

createdAt
string <date-time>
updatedAt
string <date-time>

Response samples

Content type
application/json
{
  • "payer": "customer",
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "status": "NEW",
  • "isFirstOrder": true,
  • "customerId": "string",
  • "subscriptionId": null,
  • "protocolId": null,
  • "paymentMethod": {
    },
  • "discount": "string",
  • "daysSupply": 30,
  • "orderType": "SUBSCRIPTION",
  • "shippingMethod": "STANDARD",
  • "fulfillmentInfo": {
    },
  • "lineItems": [
    ],
  • "customer": {
    },
  • "retailSubtotal": "60.00",
  • "partnerSubtotal": "36.00",
  • "customerSubtotal": "30.00",
  • "totalDiscount": "27.00",
  • "totalFees": "5.00",
  • "totalShipping": "9.99",
  • "totalTax": "0.00",
  • "totalVolumeDiscount": "12.10",
  • "customerTotal": "36.99",
  • "partnerCommission": "5.00",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

List an Order's Documents

The documents you can show the order's customer, each with a signed link: the supplement facts panel, once the fill has rendered it, then every document frozen onto the order when it was created, in that order. The panel prints the customer's name and address.

Links are private, never cached, and expire (the panel's after 15 minutes, the others after one hour). Fetch a fresh list each time a customer opens a document; never store a link.

A new order has no panel until the fill renders it, so documents can be empty. A document whose per-order copy failed is left out.

Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string
Example: 2vSGym0bH8qVEwCIGlyFoRgJq1A

Responses

Response Schema: application/json
orderId
required
string
required
Array of objects (OrderDocument)
Array
id
required
string

sfp for the supplement facts panel; otherwise the id of the document frozen onto the order

kind
required
string
Enum: "supplement_facts" "printed"

supplement_facts: the panel the fill renders for this order. printed: a document frozen onto the order when it was created.

name
required
string
url
required
string

A private, never-cached signed link that expires. Fetch a fresh one each time; never store it.

Response samples

Content type
application/json
{
  • "orderId": "string",
  • "documents": [
    ]
}

Cancel an Order

Cancel a partner-managed order that has not been dispensed. Cancelling never refunds. Cancellation propagates to fulfillment (the fill order and any shipping label are cancelled) and emits the order.cancelled webhook event.

An order can be cancelled only when all of these hold; otherwise the answer is 409 with code: ORDER_NOT_CANCELLABLE and the first failing rule in details.reason:

  • its billing mode is PARTNER_MANAGED (the mode frozen on the order at creation; a legacy order without one uses your account's current mode), else juniper_managed: contact DailyStak support to cancel it;
  • it is not already CANCELLED (already_cancelled);
  • its status is NEW or PROCESSING, else past_processing;
  • no shipping label or tracking number exists (labeled);
  • it is not on an invoice (invoiced);
  • dispensing has not begun for any of its fill orders (already_dispensed);
  • it was not paid through Shopify (shopify_paid) and has no DailyStak payment (paid_through_dailystak). The externalPaymentReference you give when confirming a renewal is your own reference, not a DailyStak payment.
Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
orderId
string

ID of the cancelled order

status
string

Order status

Response samples

Content type
application/json
{
  • "orderId": "2vTYQiGzufR4cn1cloW97DtSOYF",
  • "status": "CANCELLED"
}

Update Order Shipping Address

Replace the shipping address on an order that has not yet been filled (NEW or PROCESSING status). Amounts do not change — the order is not re-priced; only the delivery address is updated. The full order is returned.

Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required

The new shipping address.

line1
required
string
city
required
string
state
required
string

Two-letter US state code

zipCode
required
string^\d{5}$
line2
string or null

Responses

Response Schema: application/json
payer
string or null
Enum: "customer" "practice"

Who was charged for this order, frozen when it was created. customer — their own card. practice — the practice's saved card at wholesale, with no commission (a practice-paid customer; see Customer.payer). Null on orders from before the field existed, which were customer-paid.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
id
string

Unique order identifier

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Current status. AWAITING_SHIPMENT is deprecated (never written); DELIVERED is set from carrier tracking; EXCEPTION indicates a fulfillment problem.

isFirstOrder
boolean

Whether this is the linked customer's first order with your account. Computed at creation. False when the order is not linked, and false for a customer the order itself created or matched by email — only a customer referenced by customerId or sourceId is stamped.

customerId
string or null

Internal id of the Customer record this order is linked to; null when the order could not be linked.

subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. A customer with more than one subscription cannot be attributed from orderType alone — this is the id to key off.

protocolId
string or null

The protocol this order was built from; null for API orders. It survives a subscription being replaced, so it is the stabler key for grouping a customer's recurring orders.

object or null

The card that paid for this order, recorded once when the payment settled; reading the order never asks the payment provider. null when no card is recorded: orders paid before this field existed, payments that were not by card, orders with no customer payment (such as those created with POST /v2/orders), and the rare payment whose card the provider could not report at the time. Checkout and renewal orders gain it moments after they are created, so a read prompted by order.created can see null; the write that adds the card sends another order.* event for the order.

brand
string or null

Card brand

last4
string or null

Last four digits

expMonth
integer or null

Expiry month, 1-12

expYear
integer or null

Expiry year, four digits

discount
string
Deprecated

Vestigial; always "0.00"

daysSupply
integer

Number of days the order supplies

orderType
string
Enum: "ONE_TIME" "SUBSCRIPTION"
shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

Array
checkpointTime
string <date-time>

Timestamp of the checkpoint event

location
string

Location of the shipment

message
string

Status message from the carrier

tag
string

Status tag

Array of objects (LineItem)

Products included in the order

Array
id
string

Order line-item identifier. Pass it as lineItemId when creating a line-item refund.

productId
integer

Unique identifier of the product

name
string

Product name

quantity
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

price
string

Per-unit customer price

discountedPrice
string
Deprecated

Vestigial; always "0.00"

totalPrice
string

Customer price × quantity (× days supply for pack items; external items are not multiplied by days supply)

totalRetailPrice
string

Same multiplication rule at retail price

totalPartnerPrice
string

Same multiplication rule at partner (wholesale) price

totalCustomerPrice
string

Equals totalPrice

external
boolean

Whether this is an external product (powder, liquid) shipped as-is

imageUrl
string or null <uri>

Product image URL (currently always null)

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null
templateId
string or null
object

Embedded order customer snapshot (not the full Customer resource)

sourceId
string
firstName
string
lastName
string
email
string or null
phone
string or null

Digit-normalized

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

retailSubtotal
string

Sum of retail (MSRP) line totals

partnerSubtotal
string

Wholesale subtotal the partner pays

customerSubtotal
string

Customer subtotal

totalDiscount
string

Order-level customer discount amount

totalFees
string

Platform handling fees on external products. Under Juniper-managed billing these reduce commission and are not charged to the customer; under partner-managed billing they are part of the total.

totalShipping
string

Shipping charged

totalTax
string

Tax charged ("0.00" for API-created orders)

totalVolumeDiscount
string

Volume discount applied automatically from your configured tiers

customerTotal
string

Final total charged to the customer (frozen at creation)

partnerCommission
string

Frozen commission; may be negative

createdAt
string <date-time>
updatedAt
string <date-time>

Request samples

Content type
application/json
{
  • "line1": "string",
  • "line2": "string",
  • "city": "string",
  • "state": "string",
  • "zipCode": "string"
}

Response samples

Content type
application/json
{
  • "payer": "customer",
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "status": "NEW",
  • "isFirstOrder": true,
  • "customerId": "string",
  • "subscriptionId": null,
  • "protocolId": null,
  • "paymentMethod": {
    },
  • "discount": "string",
  • "daysSupply": 30,
  • "orderType": "SUBSCRIPTION",
  • "shippingMethod": "STANDARD",
  • "fulfillmentInfo": {
    },
  • "lineItems": [
    ],
  • "customer": {
    },
  • "retailSubtotal": "60.00",
  • "partnerSubtotal": "36.00",
  • "customerSubtotal": "30.00",
  • "totalDiscount": "27.00",
  • "totalFees": "5.00",
  • "totalShipping": "9.99",
  • "totalTax": "0.00",
  • "totalVolumeDiscount": "12.10",
  • "customerTotal": "36.99",
  • "partnerCommission": "5.00",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Refunds

Read the refunds on an order. Refunds are issued by DailyStak, not through the API.

List Refunds for Order

List every refund for the order (all statuses, including FAILED), oldest first, as a JSON array. Unpaginated. An unknown order ID returns an empty array, not 404.

Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string

Responses

Response Schema: application/json
Array
refundId
required
string

Unique refund identifier

orderId
required
string

Order the refund belongs to

partnerId
required
string

Owning partner

status
required
string
Enum: "PENDING" "PROCESSING" "COMPLETED" "FAILED"

Lifecycle status. Create responses always show PROCESSING; COMPLETED and FAILED are reached asynchronously. A COMPLETED refund can later flip to FAILED if the card refund fails downstream — the order's refund accounting is reversed when that happens.

refundType
required
string
Enum: "FULL" "PARTIAL"

FULL when the refund exhausts the order's remaining refundable balance (including a final refund after earlier partials)

amount
required
integer

Refund amount in cents (server-computed for full and line-item refunds)

currency
required
string

Currency the payment processor refunded in

paymentIntentId
required
string

The server-resolved payment that was refunded

reason
required
string

Partner-supplied reason (stored and echoed in webhooks/emails; not forwarded to the payment processor)

initiatedBy
required
string

OAuth client ID that created the refund

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
stripeRefundId
string or null

Payment-processor refund identifier

Array of objects or null (RefundLineItem)

Itemized breakdown when the refund was created by line items

Array
lineItemId
required
string

ID of a line item on the order — the id field on the order's lineItems

quantity
required
integer >= 1

Units being refunded. Cumulative refunded quantity may not exceed the ordered quantity.

amount
required
integer >= 1

Refund amount for this line in cents. Cumulative per-line refunds may not exceed the line's proportional share of the order's refundable total.

errorMessage
string or null

Failure reason; null unless the refund FAILED

Response samples

Content type
application/json
[
  • {
    }
]

Get Refund

Fetch a single refund. Poll this to observe the transition from PROCESSING to COMPLETED (or FAILED with errorMessage). A refund belonging to another partner, or looked up under the wrong order, is indistinguishable from a missing one (404).

Authorizations:
oauth2ClientCredentials
path Parameters
orderId
required
string
refundId
required
string

Responses

Response Schema: application/json
refundId
required
string

Unique refund identifier

orderId
required
string

Order the refund belongs to

partnerId
required
string

Owning partner

status
required
string
Enum: "PENDING" "PROCESSING" "COMPLETED" "FAILED"

Lifecycle status. Create responses always show PROCESSING; COMPLETED and FAILED are reached asynchronously. A COMPLETED refund can later flip to FAILED if the card refund fails downstream — the order's refund accounting is reversed when that happens.

refundType
required
string
Enum: "FULL" "PARTIAL"

FULL when the refund exhausts the order's remaining refundable balance (including a final refund after earlier partials)

amount
required
integer

Refund amount in cents (server-computed for full and line-item refunds)

currency
required
string

Currency the payment processor refunded in

paymentIntentId
required
string

The server-resolved payment that was refunded

reason
required
string

Partner-supplied reason (stored and echoed in webhooks/emails; not forwarded to the payment processor)

initiatedBy
required
string

OAuth client ID that created the refund

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
stripeRefundId
string or null

Payment-processor refund identifier

Array of objects or null (RefundLineItem)

Itemized breakdown when the refund was created by line items

Array
lineItemId
required
string

ID of a line item on the order — the id field on the order's lineItems

quantity
required
integer >= 1

Units being refunded. Cumulative refunded quantity may not exceed the ordered quantity.

amount
required
integer >= 1

Refund amount for this line in cents. Cumulative per-line refunds may not exceed the line's proportional share of the order's refundable total.

errorMessage
string or null

Failure reason; null unless the refund FAILED

Response samples

Content type
application/json
{
  • "refundId": "2vT4kQpRefundKsuid00000000",
  • "orderId": "string",
  • "partnerId": "string",
  • "status": "PENDING",
  • "refundType": "FULL",
  • "amount": 2500,
  • "currency": "usd",
  • "stripeRefundId": "re_3RqXYZabc123",
  • "paymentIntentId": "string",
  • "lineItems": [
    ],
  • "reason": "string",
  • "initiatedBy": "string",
  • "errorMessage": "string",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Customers

Manage customer records

Create a New Customer

Create a customer record. sourceId and email are each unique per partner — a duplicate returns 409. Email uniqueness is enforced atomically; the sourceId check is eventually consistent, so use your own sourceId as the idempotency key when retrying. Emits the customer.created webhook event.

Set managedBy to create a customer looked after by a caregiver (for example a child or a dependent adult). A managed customer may omit email; notifications go to their caregiver instead.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
firstName
required
string

Customer's first name

lastName
required
string

Customer's last name

sourceId
string

Unique customer identifier in your system

email
string <email>

Customer's email address. Required unless managedBy is set — a managed customer is reached through their caregiver instead.

phone
string

Customer's phone number

managedBy
string

Customer ID of the managing caregiver. The caregiver must belong to your partner and must not itself be managed — relationships are one level deep.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

birthYear
integer or null [ 1900 .. 2100 ]

Customer's birth year (YYYY). Used for age-based checks.

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null or omit to use partner's default discount.

Responses

Response Schema: application/json
id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

Request samples

Content type
application/json
{
  • "sourceId": "1234567890",
  • "firstName": "John",
  • "lastName": "Doe",
  • "email": "john.doe@example.com",
  • "phone": "1234567890",
  • "managedBy": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "shippingAddress": {
    },
  • "billingAddress": {
    },
  • "birthYear": 1985,
  • "discountOverride": 15
}

Response samples

Content type
application/json
{
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "sourceId": "1234567890",
  • "firstName": "John",
  • "lastName": "Doe",
  • "fullName": "John Doe",
  • "email": "john.doe@example.com",
  • "phone": "1234567890",
  • "managedBy": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "shippingAddress": {
    },
  • "billingAddress": {
    },
  • "discountOverride": 15,
  • "partnerConsent": true,
  • "payer": "customer",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

List Customers

Retrieve a paginated list of customers, look one up by exact sourceId, or search them.

sourceId is an exact, index-backed lookup over non-deleted customers: it returns zero or one customer, always with cursor null and hasMore false. It cannot be combined with search.

Search is best-effort contains-matching over name, email, phone, source ID, and customer ID (case-insensitive for name and email; exact-case for the rest), bounded to a limited scan — treat it as a convenience, not an exhaustive query. When search is provided, cursor pagination is disabled (cursor is always null) and results are capped at limit.

Pages can be shorter than limit while hasMore is still true — keep paging until hasMore is false.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 50

Page size, 1–100. Out-of-range values reset to the default.

cursor
string

Opaque pagination cursor from a previous response

sourceId
string
Example: sourceId=crm-88231

Exact, case-sensitive lookup of the non-deleted customer with this partner-supplied source ID. Returns zero or one customer; limit and cursor are ignored. Cannot be combined with search.

search
string
Example: search=john

Contains-match search across first name, last name, email, phone, source ID, and customer ID. Disables cursor pagination. Cannot be combined with sourceId.

Responses

Response Schema: application/json
required
Array of objects (Customer)
Array
id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

object (Address)

Physical address information

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

hasMore
required
boolean
cursor
string or null (Cursor)

Pagination cursor for retrieving next/previous page; null when there are no more pages

Response samples

Content type
application/json
{
  • "customers": [
    ],
  • "cursor": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "hasMore": true
}

Get Customer Details

Retrieve detailed information for a specific customer

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

Response samples

Content type
application/json
{
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "sourceId": "1234567890",
  • "firstName": "John",
  • "lastName": "Doe",
  • "fullName": "John Doe",
  • "email": "john.doe@example.com",
  • "phone": "1234567890",
  • "managedBy": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "shippingAddress": {
    },
  • "billingAddress": {
    },
  • "discountOverride": 15,
  • "partnerConsent": true,
  • "payer": "customer",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Update Customer

Update a customer with merge semantics: omitted or null fields are left unchanged, except that an explicit null clears discountOverride, birthYear and managedBy. Addresses are replaced wholesale when supplied and cannot be cleared. partnerConsent must be a boolean when supplied — never null.

Assigning managedBy puts the customer under a caregiver; clearing it promotes them back to top-level, which requires them to have an email address of their own. Either way the relationship stays one level deep: the caregiver must not itself be managed, and a customer who manages others cannot be given a caregiver. Violations return 400.

Updating the billing address also syncs it to the customer's payment profile. Emits the customer.updated webhook event.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
partnerConsent
boolean

Whether the customer consents to partner account management. Must be a boolean when supplied — never null.

firstName
string

Customer's first name

lastName
string

Customer's last name

email
string <email>

Customer's email address

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver. The caregiver must belong to your partner and must not itself be managed, and the customer being updated must not manage anyone — relationships are one level deep. Set to null to promote the customer to top-level, which requires them to have (or be given, in the same request) an email address.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

birthYear
integer or null [ 1900 .. 2100 ]

Customer's birth year (YYYY). Used for age-based checks. Set to null to clear.

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Set to null to clear override and use partner's default discount.

Responses

Response Schema: application/json
id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

Request samples

Content type
application/json
{
  • "partnerConsent": true,
  • "firstName": "John",
  • "lastName": "Doe",
  • "email": "john.doe@example.com",
  • "phone": "1234567890",
  • "managedBy": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "shippingAddress": {
    },
  • "billingAddress": {
    },
  • "birthYear": 1985,
  • "discountOverride": 15
}

Response samples

Content type
application/json
{
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "sourceId": "1234567890",
  • "firstName": "John",
  • "lastName": "Doe",
  • "fullName": "John Doe",
  • "email": "john.doe@example.com",
  • "phone": "1234567890",
  • "managedBy": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "shippingAddress": {
    },
  • "billingAddress": {
    },
  • "discountOverride": 15,
  • "partnerConsent": true,
  • "payer": "customer",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Delete Customer

Soft-delete a customer, with a cascade: their active, paused, pending, and awaiting-confirmation subscriptions are cancelled, and their not-yet-processed orders are cancelled (each cancelled order emits an order.cancelled webhook event; the subscription cancellations emit no events). Orders already in fulfillment are unaffected. The customer's sourceId and email become reusable. Emits customer.deleted.

A caregiver who still manages other customers cannot be deleted: the request returns 409 and nothing is cancelled. Delete or reassign the dependents (GET /v2/customers/{customerId}/managed) first.

Deleting an already-deleted customer returns 404.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response samples

Content type
application/json
{
  • "message": "One or more requested products were not found",
  • "code": "PRODUCTS_NOT_FOUND",
  • "details": {
    }
}

Get Customer Profile

The customer with every list an account page renders — protocols, subscriptions and recent orders — in one read.

A composite of GET /v2/customers/{customerId}, GET /v2/protocols?customerId=, GET /v2/subscriptions?customerId= and GET /v2/orders. Each entry is the same object those endpoints return, so one model per resource serves both.

protocols and subscriptions are complete and unpaged — every status, newest first. recentOrders holds at most the 20 most recent orders, newest first; a customer with more sets recentOrdersTruncated to true, and the rest are read from GET /v2/orders. Protocol line items carry a null image here.

An unknown, deleted or other partner's customer is 404, exactly as GET /v2/customers/{customerId} answers them.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
required
object (Customer)

Customer information

id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

required
Array of objects (Protocol)

Every protocol for this customer, any status, newest first

Array
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

required
Array of objects (ProtocolLineItem)

Products included in the protocol

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

required
Array of objects (Subscription)

Every subscription for this customer, any status, newest first (each with protocol: null)

Array
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

required
Array of objects (Order)

The customer's 20 most recent orders, newest first

Array
payer
string or null
Enum: "customer" "practice"

Who was charged for this order, frozen when it was created. customer — their own card. practice — the practice's saved card at wholesale, with no commission (a practice-paid customer; see Customer.payer). Null on orders from before the field existed, which were customer-paid.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

id
string

Unique order identifier

status
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

Current status. AWAITING_SHIPMENT is deprecated (never written); DELIVERED is set from carrier tracking; EXCEPTION indicates a fulfillment problem.

isFirstOrder
boolean

Whether this is the linked customer's first order with your account. Computed at creation. False when the order is not linked, and false for a customer the order itself created or matched by email — only a customer referenced by customerId or sourceId is stamped.

customerId
string or null

Internal id of the Customer record this order is linked to; null when the order could not be linked.

subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. A customer with more than one subscription cannot be attributed from orderType alone — this is the id to key off.

protocolId
string or null

The protocol this order was built from; null for API orders. It survives a subscription being replaced, so it is the stabler key for grouping a customer's recurring orders.

object or null

The card that paid for this order, recorded once when the payment settled; reading the order never asks the payment provider. null when no card is recorded: orders paid before this field existed, payments that were not by card, orders with no customer payment (such as those created with POST /v2/orders), and the rare payment whose card the provider could not report at the time. Checkout and renewal orders gain it moments after they are created, so a read prompted by order.created can see null; the write that adds the card sends another order.* event for the order.

discount
string
Deprecated

Vestigial; always "0.00"

daysSupply
integer

Number of days the order supplies

orderType
string
Enum: "ONE_TIME" "SUBSCRIPTION"
shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

Array of objects (LineItem)

Products included in the order

object

Embedded order customer snapshot (not the full Customer resource)

retailSubtotal
string

Sum of retail (MSRP) line totals

partnerSubtotal
string

Wholesale subtotal the partner pays

customerSubtotal
string

Customer subtotal

totalDiscount
string

Order-level customer discount amount

totalFees
string

Platform handling fees on external products. Under Juniper-managed billing these reduce commission and are not charged to the customer; under partner-managed billing they are part of the total.

totalShipping
string

Shipping charged

totalTax
string

Tax charged ("0.00" for API-created orders)

totalVolumeDiscount
string

Volume discount applied automatically from your configured tiers

customerTotal
string

Final total charged to the customer (frozen at creation)

partnerCommission
string

Frozen commission; may be negative

createdAt
string <date-time>
updatedAt
string <date-time>
recentOrdersTruncated
required
boolean

True when the customer has more orders than recentOrders holds; read the rest from GET /v2/orders

Response samples

Content type
application/json
{
  • "customer": {
    },
  • "protocols": [
    ],
  • "subscriptions": [
    ],
  • "recentOrders": [
    ],
  • "recentOrdersTruncated": true
}

List Managed Customers

List the customers this customer looks after as their caregiver.

Caregiver relationships are one level deep, so the result is returned whole rather than paged: cursor is always null and hasMore always false. Deleted dependents are omitted, and a customer who manages nobody returns an empty list.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
required
Array of objects (Customer)
Array
id
string

Unique customer identifier

partnerId
string

Partner identifier

sourceId
string

Unique customer identifier in the partner system

firstName
string

Customer's first name

lastName
string

Customer's last name

fullName
string

Customer's full name (firstName + lastName)

email
string or null <email>

Customer's email address. Null only for managed customers, who are reached through their caregiver's address instead.

phone
string

Customer's phone number

managedBy
string or null

Customer ID of the managing caregiver, or null when this customer manages itself.

object (Address)

Physical address information

object (Address)

Physical address information

discountOverride
integer or null [ 0 .. 100 ]

Customer-specific discount override (0-100% off MSRP). Must not exceed partner discount. Null means use partner's default discount.

partnerConsent
boolean

Whether the customer has consented to partner account management

payer
string
Default: "customer"
Enum: "customer" "practice"

Who pays for this customer's orders. customer — their own card at checkout (the default). practice — the practice's saved card at wholesale, placed in the office by the practice; such a customer never signs in and never receives email from DailyStak. Set by the practice in the portal only; ignored on create and update here. A checkout session for a practice-paid customer is refused with 409 CUSTOMER_PRACTICE_PAID.

createdAt
string <date-time>

Customer creation timestamp

updatedAt
string <date-time>

Customer last update timestamp

hasMore
required
boolean
cursor
string or null (Cursor)

Pagination cursor for retrieving next/previous page; null when there are no more pages

Response samples

Content type
application/json
{
  • "customers": [
    ],
  • "cursor": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "hasMore": true
}

Create Billing Portal Session

Create a Stripe Billing Portal session for customer self-service.

The portal allows customers to:

  • Update their payment methods
  • View billing history

If the customer doesn't have a payment profile yet, one is created automatically on first use.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
returnUrl
required
string <uri>

URL to redirect the customer after they exit the billing portal

Responses

Response Schema: application/json
url
string <uri>

Stripe Billing Portal URL. Redirect the customer to this URL to manage their payment methods.

Request samples

Content type
application/json

Response samples

Content type
application/json

Create Setup Intent (Elements)

Start collecting a replacement card on your own page. Returns a Stripe SetupIntent clientSecret: mount a Payment Element in setup mode with it and confirm with stripe.confirmSetup(...). The card it collects is saved to the customer's payment profile; call POST /v2/customers/{customerId}/payment-method with the returned setupIntentId afterwards to make it the renewal default. The SetupIntent is created for the card payment method type; which wallets the Payment Element offers in setup mode follows Stripe's behaviour for the card type.

If the customer doesn't have a payment profile yet, one is created automatically on first use.

Available to partner-hosted (Elements) checkout partners only — gated exactly like uiMode: elements sessions. No request body.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
clientSecret
required
string

Stripe SetupIntent client secret for the Payment Element (setup mode)

setupIntentId
required
string

Stripe SetupIntent id

stripePublishableKey
required
string

Publishable key for the Stripe account and mode this SetupIntent belongs to. Load Stripe.js with it rather than keeping your own copy, which would go stale at a test-to-live switch. Never null on a 200 — the same gate that 503s an unconfigured environment runs first.

Response samples

Content type
application/json
{
  • "clientSecret": "seti_1Pxyz_secret_abc123",
  • "setupIntentId": "seti_1Pxyz",
  • "stripePublishableKey": "pk_live_51Abc"
}

Get Default Payment Method

The card subscription renewals charge, so your account page can show it before the customer replaces it (POST .../setup-intent, then POST .../payment-method). Returns brand, last four and expiry only — never the payment method id.

paymentMethod is null when the customer has no payment profile or no default card. Not gated like the Elements routes: reading the card on file needs no Payment Element.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
required
object or null

The card subscription renewals charge, or null when the customer has no payment profile or no default card

brand
string or null

Card brand

last4
string or null

Last four digits

expMonth
integer or null

Expiry month, 1-12

expYear
integer or null

Expiry year, four digits

Response samples

Content type
application/json
{
  • "paymentMethod": {
    }
}

Set Default Payment Method (Elements)

Promote the card collected by a confirmed SetupIntent (from POST /v2/customers/{customerId}/setup-intent) to the customer's default payment method, so subscription renewals charge it. The previous card stays saved on the customer; only the default changes.

Call this only after Stripe has returned to your return_url with redirect_status=succeeded. The SetupIntent must have succeeded and the payment method it collected must already belong to this customer.

Gated exactly like the setup-intent route.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
setupIntentId
required
string non-empty

A confirmed SetupIntent (from POST .../setup-intent) whose payment method becomes the customer's renewal default

Responses

Response Schema: application/json
paymentMethodId
required
string

Stripe PaymentMethod id

isDefault
required
boolean

The payment method is now the customer's default for renewals (always true)

Request samples

Content type
application/json
{
  • "setupIntentId": "seti_1Pxyz"
}

Response samples

Content type
application/json
{
  • "paymentMethodId": "pm_1Pxyz",
  • "isDefault": true
}

Record a Customer Sign-In

Tells DailyStak that this customer signed in to your own app, so the sign-in appears on the customer's activity timeline beside sign-ins to a DailyStak-hosted store. No request body: DailyStak writes the entry itself and timestamps it when it records it, normally within seconds of your call, so you send no timestamp. 202 once the entry is queued. A customer that is not yours is a 404.

Authorizations:
oauth2ClientCredentials
path Parameters
customerId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response samples

Content type
application/json
{ }

Subscriptions

Create and manage customer subscriptions

List Subscriptions

List the partner's subscriptions (cursor-paginated), or search them.

Without search, results come back in creation order with an opaque cursor for the next page. With search, results are ranked fuzzy matches over customer name, subscription ID, protocol ID, and status — search is served from a secondary index that is populated asynchronously, so a just-created subscription may briefly not be searchable. Cursors from search and non-search responses are not interchangeable.

List items always have protocol: null; the linked protocol is embedded only on the single-subscription GET.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 50

Page size, 1–100. Out-of-range values reset to the default.

cursor
string

Opaque pagination cursor from a previous response

search
string

Fuzzy search over customer name, subscription ID, protocol ID, and status. Cannot be combined with customerId.

customerId
string
Example: customerId=2vTgd30JFgnwLrgb383PcV9Rken

Exact match on the customer id, served from the primary store; cannot be combined with search. Paginates like the unfiltered list, though a filtered page can come back short while a cursor remains — page until cursor is null. An unknown customer, or one belonging to another partner, returns an empty page.

Responses

Response Schema: application/json
required
Array of objects (Subscription)

Page of subscriptions (each with protocol: null)

Array
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

cursor
required
string or null

Opaque cursor for the next page; null when exhausted

hasMore
required
boolean

Response samples

Content type
application/json
{
  • "subscriptions": [
    ],
  • "cursor": "string",
  • "hasMore": true
}

Create Subscription

Create a subscription for an existing protocol and customer. Available to partner-managed-billing partners only — with Juniper-managed billing, subscriptions are created automatically by the hosted checkout flow after payment is captured (400 otherwise).

A subscription is a billing schedule that references a Protocol: the protocol owns the regimen (products and customer), the subscription owns the cadence (daysSupply — renewal orders are generated every daysSupply days) and delivery configuration. The customer must have a shipping address and must be the protocol's customer. One active subscription per protocol (409 otherwise). The subscription is created ACTIVE immediately.

generateInitialOrder requires the subscription to be due: it runs the same generation path as generate-order, which only fires when nextBillingDate is now or earlier. With the default nextBillingDate (one cycle in the future) the order generation fails with 400 — and the subscription has already been created. To create a subscription and its first order in one call, set nextBillingDate explicitly to now or earlier; generation then advances it by one cycle.

No payment is collected by this API — you bill your customer yourself. Emits the subscription.created webhook event.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
protocolId
required
string

Protocol that defines the products and pricing

customerId
required
string

Must be the protocol's customer, with a shipping address on file

startDate
string <date-time>

Defaults to now

nextBillingDate
string <date-time>

First renewal instant. Defaults to startDate plus daysSupply days. Must be now or earlier for generateInitialOrder to succeed.

endDate
string or null <date-time>
daysSupply
integer [ 1 .. 365 ]
Default: 30

Renewal cadence in days (any value 1–365)

shippingMethod
string
Default: "STANDARD"
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"
notes
string or null
generateInitialOrder
boolean
Default: false

Generate the first order in this call. Requires nextBillingDate to be now or earlier; otherwise the call returns 400 with the subscription already created.

Responses

Response Schema: application/json
required
object (Subscription)

A billing schedule that references a Protocol. The protocol owns the regimen (products and customer); the subscription owns the renewal cadence and delivery configuration. Renewal orders are generated every daysSupply days.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

orderId
required
string or null

The initial order's ID when generateInitialOrder succeeded; null otherwise

Request samples

Content type
application/json
{
  • "protocolId": "string",
  • "customerId": "string",
  • "startDate": "2019-08-24T14:15:22Z",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "notes": "string",
  • "generateInitialOrder": false
}

Response samples

Content type
application/json
{
  • "subscription": {
    },
  • "orderId": "string"
}

Get Subscription

Fetch one subscription with its linked protocol embedded under protocol (this is the only response that populates it), and the address its renewals ship to under shippingAddress.

Since 2026-10-21 a subscription keeps the address it was bought with. Updating a customer's address does not move their existing subscriptions; update each subscription's shippingAddress.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
shippingAddressSource
required
string
Enum: "subscription" "customer"

subscription when shippingAddress is the address this subscription was bought with; customer when it is the customer's current address.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

object or null

Where this subscription's renewals ship: its own address when it has one, else the customer's. Null when neither has one.

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    },
  • "shippingAddress": {
    },
  • "shippingAddressSource": "subscription"
}

Update Subscription

Update a subscription's daysSupply, shippingMethod, endDate, notes and/or shippingAddress. These are the only five fields this endpoint applies — the regimen and its pricing are managed through the protocol and amendment flow. A request that supplies none of them returns 400.

Changing daysSupply requires termsAcceptedAt on a subscription DailyStak bills: a new cadence means a new recurring amount, so you assert the customer accepted both. Price it first with GET /v2/subscriptions/{subscriptionId}/renewal-estimate so they can see the amount. Without the assertion the call returns 400 with TERMS_ACCEPTANCE_REQUIRED and nothing changes. Partner-managed billing needs no assertion (you charge your own customer). termsAcceptedAt must carry a UTC offset, be at most 5 minutes in the future and at most 24 hours old; otherwise the call returns 400 with TERMS_ACCEPTED_AT_TIMEZONE_REQUIRED, TERMS_ACCEPTED_AT_IN_FUTURE or TERMS_ACCEPTED_AT_TOO_OLD and nothing changes.

The paid cycle is never shortened. nextBillingDate is untouched: 30 → 90 on day 10 still charges on day 30, for a 90-day supply, and every 90 days after that. Nothing is prorated. nextRenewalEstimate is re-priced for the new cadence, and the customer is sent a notice (gated by the partner's subscription-renewal email toggle).

shippingMethod buys that class for the NEXT renewal only. Renewals ship STANDARD; EXPEDITED or OVERNIGHT is charged at the partner's configured price for it, and the subscription returns to STANDARD once that renewal's order is generated. STANDARD gives the upgrade back. A class with no active shipping method is refused.

No field can be cleared back to null (null means "not provided"). Last write wins; there is no status guard or optimistic locking.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string
Request Body schema: application/json
required
endDate
string <date-time>

New end date (cannot be cleared back to null)

notes
string

New notes (cannot be cleared back to null)

object or null

Where this subscription's renewals ship from now on. Changes this subscription only; the customer's address and other subscriptions are untouched. Cannot be cleared back to null. Refused with 409 SEAL_UNSUPPORTED_CHANGE on a subscription billed through Shopify.

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

daysSupply
integer [ 1 .. 365 ]

New renewal cadence, in days. Requires termsAcceptedAt on a DailyStak-billed subscription. nextBillingDate is not moved — the new cadence applies from the next advance onwards, and nothing is prorated.

shippingMethod
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

How the NEXT renewal ships. Needs no acceptance. Renewals ship STANDARD; EXPEDITED or OVERNIGHT buys that class for one renewal at the partner's configured price, then reverts to STANDARD.

termsAcceptedAt
string <date-time>

When the customer accepted the new cadence AND the new recurring amount (priced via GET /v2/subscriptions/{subscriptionId}/renewal-estimate). Required to change daysSupply on a DailyStak-billed subscription; optional, and recorded, for partner-managed billing. Must carry a UTC offset, be at most 5 minutes in the future and at most 24 hours old.

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Request samples

Content type
application/json
{
  • "endDate": "2019-08-24T14:15:22Z",
  • "notes": "string",
  • "shippingAddress": {
    },
  • "daysSupply": 90,
  • "shippingMethod": "STANDARD",
  • "termsAcceptedAt": "2026-07-25T18:05:44Z"
}

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Preview Renewal Amount

Price what a renewal of this subscription would cost at a given cadence, live. Show this to the customer before asking them to accept a cadence change, then send their acceptance as termsAcceptedAt on PUT /v2/subscriptions/{subscriptionId}.

Same shape as the subscription's nextRenewalEstimate, always basis: "quote", and priced through the same code path that charges the renewal — no promotion, the renewal shipping threshold ($9.99 under $50 of product, free at $50 or more, or a bought upgrade's own price), tax at the customer's saved address. Omit daysSupply to price the current cadence.

This is the only subscription read that prices live (every other read serves the stored snapshot), so call it when the customer is choosing, not on every render. It is an estimate, not a held quote: catalogue prices and tax rates move, so render it with its quotedAt.

If the amount cannot be priced in full — the tax provider is unreachable, the regimen cannot be priced — this returns 503 rather than an amount missing its tax. A partial figure is one the customer would accept and then be over-charged against.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string
query Parameters
daysSupply
integer [ 1 .. 365 ]
Example: daysSupply=90

Cadence to price, in days. Omitted means the subscription's current daysSupply.

Responses

Response Schema: application/json
subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Response samples

Content type
application/json
{
  • "subtotal": "120.00",
  • "shipping": "9.99",
  • "tax": "9.90",
  • "total": "129.90",
  • "currency": "usd",
  • "daysSupply": 30,
  • "quotedAt": "2019-08-24T14:15:22Z",
  • "basis": "quote"
}

Pause Subscription

Pause an active subscription. nextBillingDate is not changed — the renewal scheduler simply skips paused subscriptions. If the billing date passes while paused, the subscription becomes due immediately on resume; reschedule first if that is not what you want.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Resume Subscription

Resume a paused subscription. If nextBillingDate passed while paused, the subscription is due immediately and bills on the next scheduler run.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Cancel Subscription

Cancel a subscription from any state. Cancellation is terminal — there is no un-cancel; create a new subscription to restart. Orders already generated are unaffected.

Idempotent: cancelling an already-cancelled subscription returns 200 with the current record.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Confirm Renewal (Partner-Managed)

Confirm a renewal for a subscription in AWAITING_PARTNER state, using the one-time confirmation token delivered by the subscription.renewal_pending webhook event. The token expires 48 hours after issuance; three consecutive expired confirmations move the subscription to FAILED.

On success the renewal order is generated (no payment is collected — you have already charged your customer), the subscription returns to ACTIVE, and nextBillingDate advances by one cycle. Emits the subscription.order_created webhook event.

Confirmation is idempotent per token, but a retry may return either 200 (idempotent: true) or 201 with the same orderId — treat orderId as the deduplication key, not the status code.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string
Request Body schema: application/json
required
confirmationToken
required
string

One-time token from the subscription.renewal_pending webhook event; expires 48 hours after issuance

externalPaymentReference
string

Your payment reference for the renewal charge; stored on the subscription and threaded onto the generated order

Responses

Response Schema: application/json
orderId
required
string

The generated (or previously generated, on replay) order — fetch it via the Orders API

message
required
string
idempotent
required
boolean

True when this response replayed an earlier confirmation instead of generating a new order

Response Schema: application/json
orderId
required
string

The generated (or previously generated, on replay) order — fetch it via the Orders API

message
required
string
idempotent
required
boolean

True when this response replayed an earlier confirmation instead of generating a new order

Request samples

Content type
application/json
{
  • "confirmationToken": "string",
  • "externalPaymentReference": "string"
}

Response samples

Content type
application/json
{
  • "orderId": "string",
  • "message": "string",
  • "idempotent": true
}

Generate Renewal Order

Manually generate the next renewal order. The subscription must be active and due (nextBillingDate now or earlier — use reschedule to pull the date forward first). Line items and pricing come from the linked protocol (due scheduled changes are applied first); delivery settings come from the subscription. nextBillingDate advances by one cycle.

Partner-managed subscriptions only. No payment is collected — this is an order-generation mechanism for partner-managed billing, not a charge mechanism. A subscription DailyStak bills returns 409 and nothing changes; its renewal orders are created when the renewal payment succeeds.

The response carries the new orderId, not the full order — fetch it via the Orders API. Not idempotent: each successful call generates an order and advances the schedule.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string

Responses

Response Schema: application/json
orderId
required
string

The generated (or previously generated, on replay) order — fetch it via the Orders API

message
required
string
idempotent
required
boolean

True when this response replayed an earlier confirmation instead of generating a new order

Response samples

Content type
application/json
{
  • "orderId": "string",
  • "message": "string",
  • "idempotent": true
}

Skip Next Cycle

Skip the next delivery/billing cycle of an active subscription: nextBillingDate advances by one cycle (daysSupply days) without generating an order. Call repeatedly to skip multiple cycles. The skip is recorded in scheduleHistory with your optional reason.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string
Request Body schema: application/json
required

Send {} when no reason is given.

reason
string

Recorded in the schedule-history entry

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Reschedule Next Billing Date

Set nextBillingDate to an exact future instant. Valid for active or paused subscriptions. Subsequent cycles advance from the new date. Recorded in scheduleHistory.

Authorizations:
oauth2ClientCredentials
path Parameters
subscriptionId
required
string
Request Body schema: application/json
required
newBillingDate
required
string <date-time>

New nextBillingDate; must be strictly in the future (UTC)

reason
string

Recorded in the schedule-history entry

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

Array
property name*
additional property
any
createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

Request samples

Content type
application/json
{
  • "newBillingDate": "2019-08-24T14:15:22Z",
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "protocolId": "string",
  • "protocol": {
    },
  • "customerId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "status": "PENDING",
  • "nextBillingDate": "2019-08-24T14:15:22Z",
  • "startDate": "2019-08-24T14:15:22Z",
  • "endDate": "2019-08-24T14:15:22Z",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "lastOrderId": "string",
  • "totalOrdersGenerated": 0,
  • "notes": "string",
  • "scheduleHistory": [
    ],
  • "isActive": true,
  • "isDueForBilling": true,
  • "paymentFailureCount": 0,
  • "lastPaymentError": "string",
  • "renewalBlockedReason": "customer_not_found",
  • "renewalBlockedAt": "2019-08-24T14:15:22Z",
  • "renewalBlockedMessage": "string",
  • "cadenceTermsAcceptedAt": "2019-08-24T14:15:22Z",
  • "payer": "customer",
  • "nextRenewalEstimate": {
    }
}

Checkout

Create and manage checkout sessions

Create Checkout Session

Create a hosted checkout session: a tokenized payment page URL, valid for 24 hours, that you send your customer to complete payment.

Three mutually exclusive input modes (exactly one required):

  • protocolId — line items come from the protocol. Customer identity (email, name, phone) and shipping address auto-populate from the protocol's linked customer; request values override them, except that a mismatched customerId is ignored — the protocol's customer always wins. Creating a session for a protocol automatically cancels any older pending sessions for the same protocol (and their payment intents), so at most one session per protocol is active.
  • products — an explicit product list. customerEmail, firstName, lastName, and shippingAddress are then required.
  • cartId — lines and cadence come from a cart your shopper built with the Carts API (POST /v2/carts). Customer fields are required as in products mode, and daysSupply must be omitted (422 DAYS_SUPPLY_NOT_ALLOWED). Lines are priced at your storefront discount; in subscription mode that's the first charge only, and renewals are priced at your default discount, so a renewal can cost more than the first charge (nextRenewalEstimate shows it). Creating a session from a cart cancels that cart's previous open session and its payment intent, so a cart has at most one open session. When checkout completes, the purchased lines are removed from the cart. A missing, expired or malformed cartId, or another partner's cart, is 404 CART_NOT_FOUND; an empty cart is 422 CART_EMPTY; a cart holding a product that's no longer for sale is 422 CART_NOT_PURCHASABLE. If another session is being created from the same cart at the same moment, the call is 503 CART_BUSY with Retry-After: 1 and the other session stands; a retry creates a new session that cancels it, so if you already hold the other call's 201, keep using that session. Re-sending a request with the same Idempotency-Key returns the original session, even after payment has cleared the cart.

Creating a session prices the cart immediately (subtotal plus sales tax for the shipping address; volume and standing discounts applied — the quoted amount equals what will be charged) and creates the payment intent. Shipping is not included at creation: the customer selects a shipping method on the hosted page, which updates totalAmount.

In subscription mode, completing checkout also creates a Juniper-managed subscription with the card saved for renewals; daysSupply is required (except with cartId, where the cart's cadence is used), and subscription mode must come from a protocolId or a cartId — a products-mode subscription checkout is refused with 400.

Completion is asynchronous — after the customer pays, the order (and subscription) are created within seconds. The canonical completion signals are the order.created webhook event and, in subscription mode, subscription.created — which carries the subscriptionId, so you no longer need to poll the session for it. You may still poll until status is completed and orderId (and subscriptionId) populate.

The returned checkoutUrl has the form {origin}/checkout/{accessToken} — origin is your active, site-linked custom domain if you have one, otherwise the platform domain. The embedded access token is the only credential for the payment page: treat the URL as sensitive.

Elements mode. Send uiMode: "elements" to render the checkout on your own site with Stripe's Payment Element instead of the hosted page: the response carries clientSecret (the PaymentIntent client secret), stripePublishableKey and customerSessionClientSecret (which lets the Payment Element redisplay the customer's saved cards), and checkoutUrl is null. A shipping address is required at creation on this API, so the PaymentIntent — and therefore clientSecret — is present on the 201. Elements mode is gated per partner: 403 ELEMENTS_CHECKOUT_DISABLED unless DailyStak has enabled partner-hosted checkout for you, 409 ELEMENTS_REQUIRES_JUNIPER_MANAGED unless you are on JUNIPER_MANAGED billing, 503 ELEMENTS_CHECKOUT_NOT_CONFIGURED if the environment is not configured for it. The gate is evaluated at creation and again on every reprice call; if the switch is turned off later, an existing elements session can still be fetched and paid (and completes normally) but can no longer be repriced. Reprice the session through the /address, /shipping and /promo sub-resources.

Recurring terms on an elements subscription. A new subscription releases clientSecret only once the recurring terms are accepted. Either send the customer to checkoutUrl to accept them on our page, or present the terms on your own page and send termsAcceptedAt: your affirmation that the customer accepted them, and when. The 201 then carries recurringTerms.status: accepted and a clientSecret, and the acceptance carries over the /address, /shipping and /promo calls.

Send an Idempotency-Key header to make the call safe to retry; without one, every call creates a new session and a new payment intent.

Authorizations:
oauth2ClientCredentials
header Parameters
Idempotency-Key
string [ 1 .. 255 ] characters
Example: 5f1e8b6c-3c1e-4f5a-9d2b-7a0c4e2f81aa

Opaque key that makes this POST safe to retry. A retry carrying the same key and the same body returns the resource the first call created (201, plus Idempotent-Replayed: true) instead of creating a second one. Keys are scoped to your partner account and to this operation, and are remembered for 24 hours. The same key with a different body is a 409 IDEMPOTENCY_KEY_REUSED; a retry while the first call is still running is a 409 IDEMPOTENCY_KEY_IN_FLIGHT. A failed call releases its key, so the retry after a timeout can succeed.

Request Body schema: application/json
required
protocolId
string or null

Build the cart from this protocol's line items. Mutually exclusive with products and cartId. Older pending sessions for the same protocol are automatically cancelled.

Array of objects or null (CheckoutProductRequest)

Explicit product list. Mutually exclusive with protocolId and cartId.

Array
productId
required
integer

Catalog product ID

quantity
required
integer >= 1
packDescription
required
string
object

Per-daypart dosing; morning + afternoon + evening + sleep must equal quantity. Required for a product taken by the day, which is refused with 400 INVALID_CHECKOUT_DATA without one. Omit it for an external product (a powder or a liquid, external: true), which is taken at no time of day: its quantity is containers, priced once and never multiplied by daysSupply.

templateId
string or null

The id of your protocol template this line came from, the same key a POST /v2/protocols line takes. It is copied onto the order line, where the portal links the template and shows whether it is yours or copied from the Juniper library. An id that is not one of your templates is kept on the line but resolves to no link; it does not fail the checkout.

cartId
string or null^crt_[A-Za-z0-9_-]{22}$

Check out a cart built with the Carts API. Lines and cadence come from the cart, so daysSupply must be omitted (422 DAYS_SUPPLY_NOT_ALLOWED); customer fields are required as in products mode. Mutually exclusive with protocolId and products. Works in one_time and subscription mode. Creating a session from a cart cancels the cart's previous open session. The pattern is informational: any value that isn't one of your live carts, including one that doesn't match it, is 404 CART_NOT_FOUND, never 422.

customerEmail
string or null <email>

Required unless protocolId supplies a customer with an email

firstName
string or null

Required unless auto-populated from the protocol's customer

lastName
string or null

Required unless auto-populated from the protocol's customer

phone
string or null

10 digits (11 accepted with a leading 1)

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

mode
string
Default: "one_time"
Enum: "one_time" "subscription"
customerId
string or null

Existing platform customer, enabling saved-payment reuse and automatic first-order promotions. Ignored on protocol checkouts when it does not match the protocol's customer. Sending it vouches that the shopper IS this customer: a name sent later on PATCH .../address then renames their record. Send it only for a customer you have signed in.

daysSupply
integer or null

Required in subscription mode; also the pack-pricing multiplier. Must be omitted with cartId (the cart's cadence is used).

successUrl
string or null

Accepted and stored, but not currently used for redirection: the hosted page ends on the DailyStak confirmation page. Not applicable in elements mode (your return_url applies).

cancelUrl
string or null

Accepted and stored; not currently used for redirection

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"

Where this checkout originated

object or null

Arbitrary metadata. Values may be strings, integers, booleans, null, or nested objects/arrays of those; decimal numbers (e.g. 9.99, at any depth) are rejected with 422. Not echoed on session responses, but copied onto the resulting order and echoed on every order.* webhook event for it, where numbers (including nested ones and those inside arrays) arrive as strings while strings, booleans, null and the object/array structure are preserved. Do not put PII or secrets in it. The key campaign_token (a signed marketing campaign token) records campaign attribution when present.

property name*
additional property
any
partnerConsent
boolean
Default: false

Customer consent for partner account management; synced onto the customer record at completion

uiMode
string
Default: "hosted"
Enum: "hosted" "elements"

hosted (default): we render the checkout page; the response carries checkoutUrl. elements: your site mounts Stripe's Payment Element; the response carries clientSecret + stripePublishableKey and checkoutUrl is null. Requires the partner-hosted checkout switch (403 ELEMENTS_CHECKOUT_DISABLED otherwise) and JUNIPER_MANAGED billing (409 ELEMENTS_REQUIRES_JUNIPER_MANAGED otherwise).

termsAcceptedAt
string or null <date-time>

Elements subscriptions only (mode: subscription, uiMode: elements): your affirmation that you presented the recurring terms and the customer accepted them, and when. The session is then payable without the customer visiting checkoutUrl. Must carry a UTC offset, be at most 5 minutes in the future and at most 24 hours old; otherwise 400 with a TERMS_ACCEPTED_AT_* code.

Responses

Response Headers
Idempotent-Replayed
string
Example: "true"

true when this is the replayed result of an earlier call carrying the same Idempotency-Key; absent otherwise.

Response Schema: application/json
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
Array
id
required
string

Line-item identifier

productId
required
string

Catalog product ID (string on the wire)

name
required
string
quantity
required
integer

For pack products this is the per-day dose count

unitPrice
required
string

Customer (discounted) unit price as a decimal string

packDescription
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

Request samples

Content type
application/json
Example
{}

Response samples

Content type
application/json
{
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "customerEmail": "user@example.com",
  • "customerFirstName": "string",
  • "customerLastName": "string",
  • "customerPhone": "string",
  • "mode": "one_time",
  • "status": "pending",
  • "lineItems": [
    ],
  • "daysSupply": 0,
  • "subtotal": "22.20",
  • "shippingCost": "0.00",
  • "taxAmount": "1.83",
  • "totalAmount": "24.03",
  • "currency": "usd",
  • "nextRenewalEstimate": {
    },
  • "shippingMethod": "string",
  • "shippingAddress": {
    },
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "clientSecret": "string",
  • "stripePublishableKey": "string",
  • "customerSessionClientSecret": "string",
  • "uiMode": "hosted",
  • "stripeCheckoutSessionUrl": "string",
  • "expiresAt": "2019-08-24T14:15:22Z",
  • "orderId": "string",
  • "subscriptionId": "string",
  • "source": "link",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "viewedAt": "2019-08-24T14:15:22Z",
  • "partnerConsent": true
}

List Checkout Sessions

List your checkout sessions, newest first, cursor-paginated.

A filtered page can contain fewer than limit items (even zero) while more results remain — keep paging until nextCursor is null, not until an empty page.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 50

Page size, 1–100

cursor
string

Opaque cursor from a previous response's nextCursor

status
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Filter by session status

protocolId
string

Only sessions created from this protocol

Responses

Response Schema: application/json
required
Array of objects (CheckoutSession)
Array
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

nextCursor
required
string or null

Cursor for the next page; null when exhausted

Response samples

Content type
application/json
{
  • "sessions": [
    ],
  • "nextCursor": "string"
}

Get Checkout Session

Fetch one session. Poll this after sending the customer to the checkout page: on completion status becomes completed and orderId (and subscriptionId in subscription mode) populate.

An expired session is returned with its stored status — expiry is not eagerly applied, so compare expiresAt yourself. Another partner's session is indistinguishable from a missing one (404).

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string

Responses

Response Schema: application/json
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
Array
id
required
string

Line-item identifier

productId
required
string

Catalog product ID (string on the wire)

name
required
string
quantity
required
integer

For pack products this is the per-day dose count

unitPrice
required
string

Customer (discounted) unit price as a decimal string

packDescription
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

Response samples

Content type
application/json
{
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "customerEmail": "user@example.com",
  • "customerFirstName": "string",
  • "customerLastName": "string",
  • "customerPhone": "string",
  • "mode": "one_time",
  • "status": "pending",
  • "lineItems": [
    ],
  • "daysSupply": 0,
  • "subtotal": "22.20",
  • "shippingCost": "0.00",
  • "taxAmount": "1.83",
  • "totalAmount": "24.03",
  • "currency": "usd",
  • "nextRenewalEstimate": {
    },
  • "shippingMethod": "string",
  • "shippingAddress": {
    },
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "clientSecret": "string",
  • "stripePublishableKey": "string",
  • "customerSessionClientSecret": "string",
  • "uiMode": "hosted",
  • "stripeCheckoutSessionUrl": "string",
  • "expiresAt": "2019-08-24T14:15:22Z",
  • "orderId": "string",
  • "subscriptionId": "string",
  • "source": "link",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "viewedAt": "2019-08-24T14:15:22Z",
  • "partnerConsent": true
}

Cancel Checkout Session

Cancel a session in any non-completed state and cancel its payment intent, so the checkout link stops working. A completed session cannot be cancelled (409) — the money has moved and the order exists; refunds are issued by DailyStak.

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string

Responses

Response Schema: application/json
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
Array
id
required
string

Line-item identifier

productId
required
string

Catalog product ID (string on the wire)

name
required
string
quantity
required
integer

For pack products this is the per-day dose count

unitPrice
required
string

Customer (discounted) unit price as a decimal string

packDescription
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

Response samples

Content type
application/json
{
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "customerEmail": "user@example.com",
  • "customerFirstName": "string",
  • "customerLastName": "string",
  • "customerPhone": "string",
  • "mode": "one_time",
  • "status": "pending",
  • "lineItems": [
    ],
  • "daysSupply": 0,
  • "subtotal": "22.20",
  • "shippingCost": "0.00",
  • "taxAmount": "1.83",
  • "totalAmount": "24.03",
  • "currency": "usd",
  • "nextRenewalEstimate": {
    },
  • "shippingMethod": "string",
  • "shippingAddress": {
    },
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "clientSecret": "string",
  • "stripePublishableKey": "string",
  • "customerSessionClientSecret": "string",
  • "uiMode": "hosted",
  • "stripeCheckoutSessionUrl": "string",
  • "expiresAt": "2019-08-24T14:15:22Z",
  • "orderId": "string",
  • "subscriptionId": "string",
  • "source": "link",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "viewedAt": "2019-08-24T14:15:22Z",
  • "partnerConsent": true
}

Update Session Address (Elements)

Replace the delivery address (and optionally the billing address) of an uiMode: elements session and reprice it: tax is recalculated for the new address and the PaymentIntent amount is updated in place. Then call elements.fetchUpdates() on your page so the Payment Element shows the new amount.

Elements sessions only — a hosted session is refused with 409 ELEMENTS_SESSION_REQUIRED. The partner-hosted checkout gate is evaluated on every call, before the session is read.

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string
Request Body schema: application/json
required
line1
required
string non-empty

Street address line 1

city
required
string non-empty
state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string = 5 characters ^\d{5}$

5-digit ZIP code

line2
string or null

Street address line 2

object or null

Optional billing address when it differs from the delivery address

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

firstName
string or null non-empty

New recipient first name; the order ships to this name. Unlike the address, which this call replaces, an omitted or null firstName keeps the session's current name. An empty or whitespace-only value is a 422. If the create request sent customerId (matching the protocol's customer on a protocol checkout), the customer's record is renamed too, so later renewals ship to the new name. Otherwise only this checkout's order is affected: a customer taken from protocolId alone, or matched by email, is never renamed.

lastName
string or null non-empty

New recipient last name; same rules as firstName.

Responses

Response Schema: application/json
sessionId
required
string
subtotal
required
string

Pre-shipping, pre-tax amount with discounts applied

shippingCost
required
string
taxAmount
required
string
totalAmount
required
string

The amount the PaymentIntent now charges

effectiveDiscount
required
integer

Effective discount percent applied to the cart

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
checkoutUrl
string or null

Customer acceptance URL for new subscription checkouts.

object or null

Subscription mode only: the current estimated cost of the first renewal, so one response refreshes both numbers on your page. An address change re-estimates it; changing THIS order's shipping method or promo does not move it (a renewal ships Standard and carries no promotion). Never show totalAmount as the recurring amount.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

clientSecret
string or null

Stripe PaymentIntent client secret; null while new recurring terms require acceptance or the session can no longer be paid

appliedPromoCode
string or null

The promo code currently applied, if any

appliedPromoType
string or null
Enum: "percent" "flat"

Type of the applied promo, if any

Request samples

Content type
application/json
{
  • "line1": "123 Main St",
  • "line2": "string",
  • "city": "Austin",
  • "state": "TX",
  • "zipCode": "78701",
  • "billingAddress": {
    },
  • "firstName": "Ada",
  • "lastName": "Lovelace"
}

Response samples

Content type
application/json
{
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "subtotal": "22.20",
  • "shippingCost": "9.99",
  • "taxAmount": "1.83",
  • "totalAmount": "34.02",
  • "nextRenewalEstimate": {
    },
  • "clientSecret": "string",
  • "appliedPromoCode": "string",
  • "appliedPromoType": "percent",
  • "effectiveDiscount": 10
}

Update Session Shipping Method (Elements)

Select one of your active shipping methods on an uiMode: elements session and reprice it: shippingCost is set (the free-shipping threshold rule applies) and the PaymentIntent amount is updated in place. Then call elements.fetchUpdates() on your page.

Elements sessions only — a hosted session is refused with 409 ELEMENTS_SESSION_REQUIRED.

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string
Request Body schema: application/json
required
shippingMethodId
required
string non-empty

Id of one of your active shipping methods — use the id from GET /v2/shipping-methods.

Responses

Response Schema: application/json
sessionId
required
string
subtotal
required
string

Pre-shipping, pre-tax amount with discounts applied

shippingCost
required
string
taxAmount
required
string
totalAmount
required
string

The amount the PaymentIntent now charges

effectiveDiscount
required
integer

Effective discount percent applied to the cart

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
checkoutUrl
string or null

Customer acceptance URL for new subscription checkouts.

object or null

Subscription mode only: the current estimated cost of the first renewal, so one response refreshes both numbers on your page. An address change re-estimates it; changing THIS order's shipping method or promo does not move it (a renewal ships Standard and carries no promotion). Never show totalAmount as the recurring amount.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

clientSecret
string or null

Stripe PaymentIntent client secret; null while new recurring terms require acceptance or the session can no longer be paid

appliedPromoCode
string or null

The promo code currently applied, if any

appliedPromoType
string or null
Enum: "percent" "flat"

Type of the applied promo, if any

Request samples

Content type
application/json
{
  • "shippingMethodId": "string"
}

Response samples

Content type
application/json
{
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "subtotal": "22.20",
  • "shippingCost": "9.99",
  • "taxAmount": "1.83",
  • "totalAmount": "34.02",
  • "nextRenewalEstimate": {
    },
  • "clientSecret": "string",
  • "appliedPromoCode": "string",
  • "appliedPromoType": "percent",
  • "effectiveDiscount": 10
}

Apply Promo Code (Elements)

Validate a promo code against your promotions (case-insensitive; surrounding whitespace ignored), apply it to an uiMode: elements session and reprice it, updating the PaymentIntent amount in place. Then call elements.fetchUpdates() on your page. Applying a second code replaces the first.

Elements sessions only — a hosted session is refused with 409 ELEMENTS_SESSION_REQUIRED.

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string
Request Body schema: application/json
required
code
required
string non-empty

Promo code. Surrounding whitespace is stripped and matching is case-insensitive.

Responses

Response Schema: application/json
sessionId
required
string
subtotal
required
string

Pre-shipping, pre-tax amount with discounts applied

shippingCost
required
string
taxAmount
required
string
totalAmount
required
string

The amount the PaymentIntent now charges

effectiveDiscount
required
integer

Effective discount percent applied to the cart

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
checkoutUrl
string or null

Customer acceptance URL for new subscription checkouts.

object or null

Subscription mode only: the current estimated cost of the first renewal, so one response refreshes both numbers on your page. An address change re-estimates it; changing THIS order's shipping method or promo does not move it (a renewal ships Standard and carries no promotion). Never show totalAmount as the recurring amount.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

clientSecret
string or null

Stripe PaymentIntent client secret; null while new recurring terms require acceptance or the session can no longer be paid

appliedPromoCode
string or null

The promo code currently applied, if any

appliedPromoType
string or null
Enum: "percent" "flat"

Type of the applied promo, if any

Request samples

Content type
application/json
{
  • "code": "WELCOME10"
}

Response samples

Content type
application/json
{
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "subtotal": "22.20",
  • "shippingCost": "9.99",
  • "taxAmount": "1.83",
  • "totalAmount": "34.02",
  • "nextRenewalEstimate": {
    },
  • "clientSecret": "string",
  • "appliedPromoCode": "string",
  • "appliedPromoType": "percent",
  • "effectiveDiscount": 10
}

Remove Promo Code (Elements)

Clear the promo code applied to an uiMode: elements session and reprice it back, updating the PaymentIntent amount in place. Then call elements.fetchUpdates() on your page. If the session was created with your automatic first-order promotion, that promotion is restored rather than repricing to full price. Succeeds (200) even when no code is applied.

Elements sessions only — a hosted session is refused with 409 ELEMENTS_SESSION_REQUIRED.

Authorizations:
oauth2ClientCredentials
path Parameters
sessionId
required
string

Responses

Response Schema: application/json
sessionId
required
string
subtotal
required
string

Pre-shipping, pre-tax amount with discounts applied

shippingCost
required
string
taxAmount
required
string
totalAmount
required
string

The amount the PaymentIntent now charges

effectiveDiscount
required
integer

Effective discount percent applied to the cart

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
checkoutUrl
string or null

Customer acceptance URL for new subscription checkouts.

object or null

Subscription mode only: the current estimated cost of the first renewal, so one response refreshes both numbers on your page. An address change re-estimates it; changing THIS order's shipping method or promo does not move it (a renewal ships Standard and carries no promotion). Never show totalAmount as the recurring amount.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

clientSecret
string or null

Stripe PaymentIntent client secret; null while new recurring terms require acceptance or the session can no longer be paid

appliedPromoCode
string or null

The promo code currently applied, if any

appliedPromoType
string or null
Enum: "percent" "flat"

Type of the applied promo, if any

Response samples

Content type
application/json
{
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "subtotal": "22.20",
  • "shippingCost": "9.99",
  • "taxAmount": "1.83",
  • "totalAmount": "34.02",
  • "nextRenewalEstimate": {
    },
  • "clientSecret": "string",
  • "appliedPromoCode": "string",
  • "appliedPromoType": "percent",
  • "effectiveDiscount": 10
}

Create Subscription Checkout from Protocol

Create a hosted checkout session for a recurring subscription built from a protocol's current regimen. Send the customer to the returned checkoutUrl (valid 24 hours); on completion of payment a subscription is created and linked to this protocol, with the card saved for renewals. Older pending sessions for the same protocol are automatically cancelled.

daysSupply and shippingMethod are supplied at checkout time (they are not stored on the protocol) and determine the resulting subscription's billing cadence and shipping. shippingMethod is matched case-insensitively against your configured shipping-method names.

Completion is asynchronous — listen for the order.created webhook event or poll the session.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
daysSupply
integer [ 1 .. 365 ]
Default: 30

Number of days each order supplies (determines billing frequency)

shippingMethod
string
Default: "STANDARD"

Shipping method name (canonically STANDARD, EXPEDITED, or OVERNIGHT), matched case-insensitively against your configured shipping methods

successUrl
string or null

Redirect URL after successful checkout

cancelUrl
string or null

Redirect URL when checkout is cancelled

object or null

Additional metadata to attach to the checkout session

property name*
additional property
any

Responses

Response Schema: application/json
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
Array
id
required
string

Line-item identifier

productId
required
string

Catalog product ID (string on the wire)

name
required
string
quantity
required
integer

For pack products this is the per-day dose count

unitPrice
required
string

Customer (discounted) unit price as a decimal string

packDescription
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

Request samples

Content type
application/json
{
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "successUrl": "string",
  • "cancelUrl": "string",
  • "metadata": { }
}

Response samples

Content type
application/json
{
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "customerEmail": "user@example.com",
  • "customerFirstName": "string",
  • "customerLastName": "string",
  • "customerPhone": "string",
  • "mode": "one_time",
  • "status": "pending",
  • "lineItems": [
    ],
  • "daysSupply": 0,
  • "subtotal": "22.20",
  • "shippingCost": "0.00",
  • "taxAmount": "1.83",
  • "totalAmount": "24.03",
  • "currency": "usd",
  • "nextRenewalEstimate": {
    },
  • "shippingMethod": "string",
  • "shippingAddress": {
    },
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "clientSecret": "string",
  • "stripePublishableKey": "string",
  • "customerSessionClientSecret": "string",
  • "uiMode": "hosted",
  • "stripeCheckoutSessionUrl": "string",
  • "expiresAt": "2019-08-24T14:15:22Z",
  • "orderId": "string",
  • "subscriptionId": "string",
  • "source": "link",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "viewedAt": "2019-08-24T14:15:22Z",
  • "partnerConsent": true
}

Create One-Time Order Checkout from Protocol

Create a hosted checkout session for a single (one-time) order built from a protocol's current regimen. Unlike the subscription checkout, no recurring billing is set up — completing checkout produces a single order. Sessions expire after 24 hours; older pending sessions for the same protocol are automatically cancelled.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
daysSupply
integer [ 1 .. 365 ]
Default: 30

Number of days each order supplies (determines billing frequency)

shippingMethod
string
Default: "STANDARD"

Shipping method name (canonically STANDARD, EXPEDITED, or OVERNIGHT), matched case-insensitively against your configured shipping methods

successUrl
string or null

Redirect URL after successful checkout

cancelUrl
string or null

Redirect URL when checkout is cancelled

object or null

Additional metadata to attach to the checkout session

property name*
additional property
any

Responses

Response Schema: application/json
sessionId
required
string

Session identifier

partnerId
required
string
customerEmail
required
string <email>
mode
required
string
Enum: "one_time" "subscription"
status
required
string
Enum: "pending" "viewed" "processing" "completed" "failed" "expired" "cancelled"

Session lifecycle. processing and the terminal states are set by the asynchronous completion flow. Expiry is judged from expiresAt; the stored status is not eagerly flipped to expired.

required
Array of objects (CheckoutLineItem)
Array
id
required
string

Line-item identifier

productId
required
string

Catalog product ID (string on the wire)

name
required
string
quantity
required
integer

For pack products this is the per-day dose count

unitPrice
required
string

Customer (discounted) unit price as a decimal string

packDescription
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

subtotal
required
string

Pre-shipping, pre-tax amount as a decimal string, with volume and standing discounts applied. Pack products price per day of supply; external products count once per cycle.

shippingCost
required
string

Shipping charged on this purchase, as a decimal string; "0.00" until a shipping method is selected.

taxAmount
required
string

Sales tax on this purchase, as a decimal string; "0.00" until an address is known.

totalAmount
required
string

Amount to be charged, as a decimal string: always subtotal + shippingCost + taxAmount, as on the reprice responses. Shipping is added when a method is selected. The charge always equals this amount at confirmation time.

currency
required
string
checkoutUrl
required
string or null

Hosted payment page URL ({origin}/checkout/{accessToken}). The embedded token is the only credential for the page — treat the URL as sensitive. New subscription sessions also return this URL for explicit recurring-terms acceptance when uiMode: elements. One-time and historical Elements sessions keep a null URL. A usable store slug or linked custom domain is required for new subscriptions.

uiMode
required
string
Enum: "hosted" "elements"

Who renders the checkout — the hosted page, or your own site

expiresAt
required
string <date-time>

Creation time + 24 hours; expired sessions cannot complete

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
partnerConsent
required
boolean
customerFirstName
string or null
customerLastName
string or null
customerPhone
string or null

Normalized to 10 digits

daysSupply
integer or null
object or null

Subscription mode only: what the FIRST renewal is expected to cost. Show this — not totalAmount — as the recurring amount on your page. totalAmount is today's charge and may carry an automatic first-order promotion, which never applies to a renewal. Null for one-time sessions and until a shipping address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

shippingMethod
string or null

For generic sessions, null until the customer selects a method on the hosted page. Protocol-checkout sessions are created with the method already resolved and echo its name.

object (Address)

Physical address information

line1
required
string <= 100 characters

Primary address line

city
required
string <= 50 characters

City name

state
required
string = 2 characters

Two-letter US state code (the 50 states, DC, and PR; territories like GU/VI and military codes are not accepted)

zipCode
required
string^\d{5}$

Postal/ZIP code (5 digits)

line2
string or null <= 100 characters

Secondary address line (optional)

object or null

Customer authorization for this frozen purchase; new subscriptions require explicit checkout-token acceptance before a client secret is available.

required
required
boolean
status
required
string
Enum: "acceptance_required" "accepted" "legacy_unknown" "not_required"
purchaseRevision
string or null
policyVersion
string or null
disclosureHash
string or null
disclosureText
string or null

Exact server-owned text to display; may be absent until an address permits purchase preparation.

acceptedAt
string or null <date-time>
clientSecret
string or null

Stripe PaymentIntent client secret. Populated only for uiMode: elements sessions, only while the session is awaiting payment (status pending/viewed), and only on create and get (the reprice endpoints return it on RepricedSession) — null on list and cancel, and null for hosted sessions. New subscription sessions also return null until the customer accepts the current frozen purchase through its checkout-token endpoint.

stripePublishableKey
string or null

DailyStak's Stripe publishable key; populated only for uiMode: elements sessions. Pass it to loadStripe() on your site. Null for hosted sessions.

customerSessionClientSecret
string or null

Stripe CustomerSession client secret, which lets the Payment Element redisplay the cards already saved for this customer. Pass it to stripe.elements() alongside clientSecret. Same gate as clientSecret — uiMode: elements only, only while the session is awaiting payment (status pending/viewed), and only on create and get; null on list, cancel and the reprice responses, and null for a session with no payment profile. Short-lived: mint it per page render rather than storing it. Only cards vaulted by a previous subscription checkout or by POST /v2/customers/{customerId}/payment-method are shown — one-time checkouts do not save a card.

stripeCheckoutSessionUrl
string or null

Legacy field; always null

orderId
string or null

Set when the session completes

subscriptionId
string or null

Set when a subscription-mode session completes

source
string or null
Enum: "link" "widget" "redirect" "api" "storefront"
viewedAt
string or null <date-time>

May remain null; page-view tracking is not consistently populated

Request samples

Content type
application/json
{
  • "daysSupply": 30,
  • "shippingMethod": "STANDARD",
  • "successUrl": "string",
  • "cancelUrl": "string",
  • "metadata": { }
}

Response samples

Content type
application/json
{
  • "sessionId": "2vTgd30JFgnwLrgb383PcV9Rken",
  • "partnerId": "string",
  • "customerEmail": "user@example.com",
  • "customerFirstName": "string",
  • "customerLastName": "string",
  • "customerPhone": "string",
  • "mode": "one_time",
  • "status": "pending",
  • "lineItems": [
    ],
  • "daysSupply": 0,
  • "subtotal": "22.20",
  • "shippingCost": "0.00",
  • "taxAmount": "1.83",
  • "totalAmount": "24.03",
  • "currency": "usd",
  • "nextRenewalEstimate": {
    },
  • "shippingMethod": "string",
  • "shippingAddress": {
    },
  • "recurringTerms": {
    },
  • "checkoutUrl": "string",
  • "clientSecret": "string",
  • "stripePublishableKey": "string",
  • "customerSessionClientSecret": "string",
  • "uiMode": "hosted",
  • "stripeCheckoutSessionUrl": "string",
  • "expiresAt": "2019-08-24T14:15:22Z",
  • "orderId": "string",
  • "subscriptionId": "string",
  • "source": "link",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z",
  • "viewedAt": "2019-08-24T14:15:22Z",
  • "partnerConsent": true
}

Carts

Carts for your own storefront or app

Create Cart

Create an empty cart for your own storefront or app: a 30-day cadence, no lines, revision: 0. The response carries cartSecret, which lets the shopper's browser or app read and edit this one cart on the public cart host. The secret is returned only here and by POST /v2/carts/{cartId}/secret, so store it wherever you store the cartId.

A cart expires after 90 days without a change (expiresAt); after that it reads as 404 CART_NOT_FOUND. There is no idempotency on this call: a retried create leaves an empty cart that expires on its own. Responses are sent with Cache-Control: no-store.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

cartSecret
required
string

The bearer credential for this cart on the public cart host. Returned only here and on rotation, so store it with the cartId. 70 characters; treat it as opaque.

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false,
  • "cartSecret": "crt_2mH3kQ7vX9pL4nR8sT1uWy.Zk8pQ2wR7xT4nV1bY6cM3hJ9dF5gL0sA2eU8iO4kW7r"
}

Get Cart

Read the whole priced cart. Two ways to call it:

  • Server-side, on the partner API host (https://api.fulfillment.juniperhealth.com, sandbox https://api.fulfillment.sandbox.juniperhealth.com), with your access token (order/v2.read). No secret needed; another partner's cart, or a cartId that isn't a cart id, is 404 CART_NOT_FOUND. Token failures are the usual gateway 401/403.
  • From the shopper's browser or app, on the public cart host (https://cart.fulfillment.juniperhealth.com, sandbox https://cart.fulfillment.sandbox.juniperhealth.com), with Authorization: Bearer <cartSecret>. Any origin may call it (CORS *). A secret for a different cart than cartId is 401 CART_SECRET_INVALID.

Reading never changes revision or extends expiresAt. The response never carries cartSecret, and is sent with Cache-Control: no-store.

Authorizations:
oauth2ClientCredentialscartSecret
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false
}

Set Cart Cadence

Set the cart's cadence: 30, 60 or 90 days of supply per shipment. This is the cadence a checkout from the cart uses. It always writes and increments revision, even when the value is unchanged.

Shopper route: public cart host, Authorization: Bearer <cartSecret>. A value other than 30, 60 or 90, a number that isn't a plain integer (60.0, "60", true), or any other key in the body is 422 VALIDATION_FAILED.

Authorizations:
cartSecret
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

Request Body schema: application/json
required
daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

Request samples

Content type
application/json
{
  • "daysSupply": 60
}

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false
}

Set Cart Line

Set one line, replacing it if the product is already in the cart. The write is absolute ("set this line to 3"), so repeating it is safe and overlapping writes end in the state of the last one.

Send exactly one of:

  • administration for a product dispensed by dose: morning, afternoon, evening and sleep counts, at least one above zero. The daily total is checked against the product's dose cap (maxDailyUnits on the line); above the recommended amount but within the cap is allowed and sets doseWarning: true on the response.
  • unitsPerCycle for a pre-packaged product: 1-50 units per shipment.

Sending the one that doesn't match the product is 422 LINE_MODE_MISMATCH. A cart holds at most 50 distinct products. Rewriting a line that came from a template keeps its protocolTemplateId.

The body is strict: an unknown key (such as a misspelled daypart) or a number that isn't a plain integer (true, "2", 2.0) is 422 VALIDATION_FAILED. A productId that isn't a plain integer of at most 20 digits is 422 PRODUCT_NOT_FOUND.

Shopper route: public cart host, Authorization: Bearer <cartSecret>.

Authorizations:
cartSecret
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

productId
required
string
Example: 11234

Catalog product ID, written as a plain integer of at most 20 digits with no sign or leading zeros

Request Body schema: application/json
required
object or null

The daily dose, for a product dispensed by dose

morning
integer >= 0
Default: 0
afternoon
integer >= 0
Default: 0
evening
integer >= 0
Default: 0
sleep
integer >= 0
Default: 0
unitsPerCycle
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product. A plain integer: true, "2" and 2.0 are refused.

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

Request samples

Content type
application/json
Example
{
  • "administration": {
    }
}

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false
}

Remove Cart Line

Remove one line. Removing a line that isn't in the cart changes nothing: the response is the current cart at its current revision. A productId that isn't a plain integer of at most 20 digits can't be in the cart, so it's the same no-op.

Shopper route: public cart host, Authorization: Bearer <cartSecret>.

Authorizations:
cartSecret
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

productId
required
string
Example: 11234

Catalog product ID, written as a plain integer of at most 20 digits with no sign or leading zeros

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false
}

Add Template to Cart

Add every line of one of your ACTIVE protocol templates in one write. Publishing the template to a hosted storefront isn't required; DailyStak library templates can't be added (copy one first).

Each added line records protocolTemplateId. A product already in the cart is replaced by the template's line. All or nothing: if any template line can't be bought, nothing is added (422 TEMPLATE_NOT_PURCHASABLE, the lines in details.lines), and a dose over a product's cap is 422 DOSE_CAP_EXCEEDED naming details.productId. The 50-product limit counts the cart's products and the template's together. A templateId that isn't one of your ACTIVE templates (including any id over 128 characters), or a template with no lines, is 404 PROTOCOL_TEMPLATE_NOT_FOUND.

Shopper route: public cart host, Authorization: Bearer <cartSecret>.

Authorizations:
cartSecret
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

templateId
required
string

One of your protocol templates (GET /v2/protocol-templates). An id over 128 characters is 404 PROTOCOL_TEMPLATE_NOT_FOUND.

Responses

Response Schema: application/json
cartId
required
string

The cart's id, crt_ plus 22 characters

revision
required
integer >= 0

Increments on every change, including checkout completion removing the purchased lines; 0 for a new cart. Removing a line that isn't there and rotating the secret leave it unchanged.

daysSupply
required
integer
Enum: 30 60 90

Days of supply per shipment; a checkout from the cart uses it

required
Array of objects (CartLine)

The cart's lines, in product ID order

Array
productId
required
string
name
required
string
image
required
string or null

Product image URL

required
object or null

The daily dose, for a product dispensed by dose; null for a pre-packaged product

unitsPerCycle
required
integer or null [ 1 .. 50 ]

Units per shipment, for a pre-packaged product; null for a product dispensed by dose

unitsPerShipment
required
integer

What one shipment contains: the daily dose × daysSupply, or unitsPerCycle

customerPrice
required
string

Your storefront price per unit, as a decimal string in dollars: at least 2 decimal places, digits below a cent kept, never rounded or in exponent notation ("16.00", "0.40", "0.0833")

retailPrice
required
string

The catalog retail price per unit, as a decimal string in the same format as customerPrice

maxDailyUnits
required
integer or null

The dose cap: the most units a day administration may add up to. Null for a pre-packaged product.

protocolTemplateId
required
string or null

The template this line was added from, kept when the line's dose or units change later; null for a line added on its own

subtotal
required
string

The cart's total for one shipment at your storefront rates, as a decimal string in the same format as customerPrice. It is priced for the cart as a whole, so it can differ slightly from adding up customerPrice × unitsPerShipment. It excludes shipping, tax, promo codes and volume discounts, and may be up to 5 minutes behind a catalog price change. The checkout total is authoritative; a one-time checkout can differ from this by rounding.

currency
required
string
updatedAt
required
string or null <date-time>

When the cart or any of its lines last changed

expiresAt
required
string or null <date-time>

When the cart expires: 90 days after its last change. Changes move it out; reads don't.

doseWarning
required
boolean

true on the response to a write whose dose is above a product's recommended amount but within its cap; always false on reads

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "revision": 7,
  • "daysSupply": 30,
  • "items": [
    ],
  • "subtotal": "25.50",
  • "currency": "usd",
  • "updatedAt": "2026-09-23T18:00:00Z",
  • "expiresAt": "2026-12-22T18:00:00Z",
  • "doseWarning": false
}

Rotate Cart Secret

Replace the cart's secret and return the new one. The old secret stops working immediately (401 CART_SECRET_INVALID), so hand the new one to the shopper's browser or app. Rotating doesn't change the cart, its revision or its expiry.

Don't rotate the same cart's secret concurrently. The last rotation wins, and the other caller still gets a 200 whose secret is already dead.

The response is sent with Cache-Control: no-store.

Authorizations:
oauth2ClientCredentials
path Parameters
cartId
required
string^crt_[A-Za-z0-9_-]{22}$
Example: crt_2mH3kQ7vX9pL4nR8sT1uWy

The cart's id (crt_ plus 22 characters), from POST /v2/carts. The pattern is informational: a value that doesn't match is never a 422. On the partner routes it's 404 CART_NOT_FOUND; on the shopper routes, a cart secret belongs to one cart, so any other cartId is 401 CART_SECRET_INVALID.

Responses

Response Schema: application/json
cartId
required
string
cartSecret
required
string

The new secret. The previous one no longer works.

Response samples

Content type
application/json
{
  • "cartId": "crt_2mH3kQ7vX9pL4nR8sT1uWy",
  • "cartSecret": "crt_2mH3kQ7vX9pL4nR8sT1uWy.Hn5tB8vC1xZ4mQ7wE2rT9yU6iO3pA0sD5fG8hJ1kL4z"
}

Protocols

Manage customer regimens (protocols) — the customer reference plus their assigned product line items; checkout and subscriptions build on them

Create a New Protocol

Create a protocol: a customer's regimen — the customer reference plus the product line items a practitioner has assigned them. Shipping and billing cadence are not part of a protocol; they are chosen at checkout or on the subscription.

A customer may hold several active protocols. Once a protocol has a live subscription, direct edits are blocked — use the amendment flow.

A protocol can include each product only once: lineItems that list a product more than once are rejected with 400 (<name> is listed more than once. A protocol can include each product only once.).

Emits the protocol.created webhook event.

Send an Idempotency-Key header to make the call safe to retry; without one, a timeout retry creates a second active protocol.

Authorizations:
oauth2ClientCredentials
header Parameters
Idempotency-Key
string [ 1 .. 255 ] characters
Example: 5f1e8b6c-3c1e-4f5a-9d2b-7a0c4e2f81aa

Opaque key that makes this POST safe to retry. A retry carrying the same key and the same body returns the resource the first call created (201, plus Idempotent-Replayed: true) instead of creating a second one. Keys are scoped to your partner account and to this operation, and are remembered for 24 hours. The same key with a different body is a 409 IDEMPOTENCY_KEY_REUSED; a retry while the first call is still running is a 409 IDEMPOTENCY_KEY_IN_FLIGHT. A failed call releases its key, so the retry after a timeout can succeed.

Request Body schema: application/json
required
customerId
required
string

ID of an existing customer

required
Array of objects (LineItemRequest) non-empty

Products included in the protocol

Array (non-empty)
productId
required
integer

Unique identifier of the product

quantity
required
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null <= 500 characters

Provider instructions for this line

templateId
string or null

Protocol-template provenance identifier

name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

Responses

Response Headers
Idempotent-Replayed
string
Example: "true"

true when this is the replayed result of an earlier call carrying the same Idempotency-Key; absent otherwise.

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Request samples

Content type
application/json
{
  • "customerId": "2OKpLhTlpCrQHabz5X9qRbH4abc",
  • "lineItems": [
    ]
}

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

List Protocols

Retrieve a paginated list of protocols, or search them. Search matches customer name, protocol ID, line-item product names, and practitioner pack labels; pack-label-only matches rank below any product-name substring match. Search ignores direction, uses its own cursor format (not interchangeable with the plain list's), and is served from an asynchronously populated index.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 10

Maximum number of results per page

cursor
string

Pagination cursor for retrieving next/previous page

direction
string
Enum: "next" "previous"

Direction for pagination (next or previous)

search
string
Example: search=John Doe

Fuzzy search over customer name, protocol ID, product names, and practitioner pack labels; pack-label-only matches rank below any product-name substring match. Cannot be combined with customerId.

customerId
string
Example: customerId=2vTgd30JFgnwLrgb383PcV9Rken

Exact match on the customer id, served from the primary store; cannot be combined with search. Paginates like the unfiltered list (direction does not apply), though a filtered page can come back short while a cursor remains — page until cursor is null. An unknown customer, or one belonging to another partner, returns an empty page.

Responses

Response Schema: application/json
Array of objects (Protocol)
Array
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

required
Array of objects (ProtocolLineItem)

Products included in the protocol

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

cursor
string or null (Cursor)

Pagination cursor for retrieving next/previous page; null when there are no more pages

Response samples

Content type
application/json
{
  • "protocols": [
    ],
  • "cursor": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Get Protocol Details

Retrieve detailed information for a specific protocol

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Update Protocol

Replace a protocol's line items. Only lineItems can be updated — the customer link is fixed, and shipping/cadence live on the subscription. Omitting lineItems is a no-op that returns the current protocol.

Once the protocol has a live subscription, direct edits are blocked (409) — propose an amendment instead, which routes regimen changes through customer consent.

A protocol can include each product only once: lineItems that list a product more than once are rejected with 400, naming the product.

Emits the protocol.updated webhook event.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
Array of objects (LineItemRequest) non-empty

Products included in the protocol

Array (non-empty)
productId
required
integer

Unique identifier of the product

quantity
required
integer >= 1

Quantity of the product

packDescription
string

Description of the product packaging

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

takeWith
string or null
Enum: "food" "water" "empty_stomach"

Recommended intake instruction

note
string or null <= 500 characters

Provider instructions for this line

templateId
string or null

Protocol-template provenance identifier

name
string or null <= 80 characters

Optional practitioner-facing label. Null clears it.

Responses

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Request samples

Content type
application/json
{
  • "lineItems": [
    ]
}

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Archive Protocol

Archive an INACTIVE protocol. Archived is terminal — it cannot be reactivated. A protocol that is still ACTIVE returns 409: deactivate it first (POST /v2/protocols/{protocolId}/deactivate), which cancels its subscriptions, open checkout sessions, and pending changes.

Emits the protocol.archived webhook event with the full protocol.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken

Responses

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Deactivate Protocol

ACTIVE → INACTIVE. Cancels every subscription on the protocol that is not already cancelled (the customer will not be charged again), cancels pending/viewed checkout sessions, and cancels draft/pending amendments and open scheduled steps. Orders already placed are untouched. The customer receives one consolidated notice (gated by the partner's sendSubscriptionCancelledEmails setting).

Reversible via reactivate. Left untouched, an INACTIVE protocol is archived automatically after 60 days (archivesAt). Any edit restarts that clock.

Emits protocol.deactivated carrying the protocol plus sweep counts.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string

Responses

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

required
object (DeactivationSweep)

What a deactivation cancelled

subscriptionsCancelled
required
integer
checkoutSessionsCancelled
required
integer
amendmentsCancelled
required
integer
scheduledChangesCancelled
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z",
  • "sweep": {
    }
}

Reactivate Protocol

INACTIVE → ACTIVE, status only. No subscription is resumed: create a new checkout session for the protocol and the customer's purchase creates the new subscription. Emits protocol.reactivated.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string

Responses

Response Schema: application/json
id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": "2vSGym0bH8qVEwCIGlyFoRgJq1A",
  • "customer": {
    },
  • "status": "ACTIVE",
  • "name": "string",
  • "inactivatedAt": "2019-08-24T14:15:22Z",
  • "archivesAt": "2019-08-24T14:15:22Z",
  • "orderType": "SUBSCRIPTION",
  • "lineItems": [
    ],
  • "hasExternal": true,
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Preview Amendment Cost

Dry-run the cost impact of a proposed set of regimen edits without persisting anything. The edits are folded onto an in-memory copy of the protocol's active regimen and re-priced, returning current vs. proposed totals and partner commission, plus a per-line breakdown.

Scope is the regimen subtotal and commission only — promo, fees, shipping, and tax are applied at order time and are excluded here. An empty changes list yields a no-op preview (proposed == current).

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
daysSupply
integer or null [ 1 .. 365 ]

Read-only supply estimate; defaults to the live subscription, protocol, then 30 days.

Array of objects (AmendmentChange) <= 99 items
Default: []
Array (<= 99 items)
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason

Responses

Response Schema: application/json
currentTotal
required
string

Customer-facing subtotal of the current active regimen

proposedTotal
required
string

Customer-facing subtotal of the proposed active regimen

priceChange
required
string

proposedTotal - currentTotal (signed)

commissionCurrent
required
string

Partner earnings on the current regimen

commissionProposed
required
string

Partner earnings on the proposed regimen

commissionChange
required
string

commissionProposed - commissionCurrent (signed)

object or null

Practitioner view of a quote or receipt; never send to patients.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object or null

Practitioner view of a quote or receipt; never send to patients.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
Array of objects (AmendmentCostPreviewLine)

Per-line breakdown of the proposed active regimen

Array
productId
required
integer
quantity
required
integer

Quantity (per-day for packs)

lineSubtotal
required
string

Customer-facing subtotal for this line

lineCommission
required
string

Partner earnings (customer price minus wholesale) for this line

Request samples

Content type
application/json
{
  • "daysSupply": 1,
  • "changes": [ ]
}

Response samples

Content type
application/json
{
  • "currentPricingBreakdown": {
    },
  • "proposedPricingBreakdown": {
    },
  • "currentTotal": "120.00",
  • "proposedTotal": "150.00",
  • "priceChange": "30.00",
  • "commissionCurrent": "30.00",
  • "commissionProposed": "38.00",
  • "commissionChange": "8.00",
  • "proposedLines": [
    ]
}

Products

Access product catalog and inventory details

Get Product Catalog

Retrieve a paginated list of available products, or search the catalog.

Search is fuzzy over product name, search terms, description, and ingredients, and returns ranked results with totalHits. In search mode the direction, updated_after, and updated_before parameters are ignored. Note the time-range filters use snake_case parameter names.

Products this partner has disabled are excluded unless include_disabled=true; every item carries enabled.

Filters: form and categories match ANY listed value; certifications and allergens (free-from claims) match ALL. Parameters AND together, and with status and the updated range. Unknown values are ignored. A filtered page holds limit products unless the catalog runs out, and cursor is returned only when more products match. In search mode the filters apply inside the search.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 10

Maximum number of results per page

cursor
string

Pagination cursor for retrieving next/previous page

direction
string
Enum: "next" "previous"

Direction for pagination (next or previous)

status
string
Default: "ACTIVE"
Enum: "ACTIVE" "INACTIVE" "DRAFT" "OUT_OF_STOCK" "DISCONTINUED"

Filter products by status (default ACTIVE)

form
string
Example: form=Capsule,Veg Capsule

Comma-separated product forms; returns products in ANY of them. A value is the stored form (Capsule, Quick-Dissolve Tablet, Powder, Liquid, Serum, Softgel, Tablet, Veg Capsule) or its name (CAPSULE, QUICK_DISSOLVE_TABLET, POWDER, LIQUID, SERUM, SOFTGEL, TABLET, VEG_CAPSULE), in any case. Unknown values are ignored.

certifications
string
Example: certifications=vegan,kosher

Comma-separated certifications; returns products carrying ALL of them. Values: certified_gmp, organic_certified, halal, kosher, lab_tested, manufactured_in_usa, non_gmo, sustainably_sourced, vegan, vegetarian, or their names (CERTIFIED_ORGANIC means organic_certified), in any case. Unknown values are ignored.

allergens
string
Example: allergens=gluten_free,dairy_free

Comma-separated free-from claims; returns products carrying ALL of them, so gluten_free,dairy_free means both labels are present (a must-match, not an exclusion). Values: animal_free, corn_free, dairy_free, egg_free, fish_free, gluten_free, hexane_free, nut_free, peanut_free, sesame_free, shellfish_free, soy_free, treenut_free, wheat_free, yeast_free, or their names (TREE_NUT_FREE means treenut_free), in any case. Unknown values are ignored.

categories
string
Example: categories=Heart health,Vitamins

Comma-separated shop categories; returns products in ANY of them. Values: Heart health, Metabolic health, Immune support, Inflammation & antioxidants, Brain & mood, Stress & sleep, Gut health, Liver & detox, Energy & performance, Healthy aging, Bones & joints, Hair, skin & nails, Hormones & thyroid, Women's health, Men's health, Eye health, Vitamins, Minerals, Herbs, or their names (HAIR_SKIN_AND_NAILS), in any case. "Hair, skin & nails" contains a comma and is still read as one value; URL-encode the "&". Unknown values are ignored.

updatedAfter
string
Example: updatedAfter=2026-01-01T00:00:00Z

Only products updated at or after this ISO 8601 timestamp (snake_case parameter name)

updatedBefore
string
Example: updatedBefore=2026-06-30T23:59:59Z

Only products updated at or before this ISO 8601 timestamp (snake_case parameter name)

search
string
Example: search=omega

Fuzzy search products by name, ingredients, description, or search terms

customerId
string
Example: customerId=2QKRPxjjaxbGjOPF8TfCQb7YOAZ

Optional customer ID for customer-specific pricing. When provided, uses the customer's discount override (if set) instead of the partner's default discount.

includeDisabled
boolean
Default: false

Include products this partner has disabled (snake_case parameter name). Disabled products are excluded from this listing by default. Filtering is applied after pagination, so a page containing disabled products returns fewer than limit items; the cursor still advances correctly.

Responses

Response Schema: application/json
Array of objects (Product)
Array
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

id
integer

Unique product identifier

name
string

Product name

description
string

Detailed product description

packDescription
string

Description of the product packaging

object (PricingInfo)

Price breakdown showing partner and customer pricing.

Pricing Formula:

  • partnerPrice = MSRP × (1 - partnerDiscount%)
  • customerPrice = MSRP × (1 - customerDiscount%)
  • commission = customerPrice - partnerPrice

The customer discount comes from the partner's default discount setting, or can be overridden per-customer via customer.discountOverride.

external
boolean

If the product is external to the pill packs

image
string <uri>

URL to the product image

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

price
string or null

Retail (MSRP) unit price — the same value as pricing.retailPrice. Display it alongside your partner price (struck through, for example) to show the discount your customers receive.

form
string
Enum: "Capsule" "Veg Capsule" "Tablet" "Quick-Dissolve Tablet" "Softgel" "Powder" "Liquid" "Serum"

Physical form of the supplement (display-cased)

certifications
Array of strings

Product certifications

allergens
Array of strings

Allergy related information

categories
Array of strings

The shop categories the product belongs to, staff-assigned from a fixed list of nineteen (initially backfilled from its Shopify collections). Empty when none are set.

object (SupplementFactsPanel)

Detailed nutritional and ingredient information

status
string
Enum: "ACTIVE" "INACTIVE" "DRAFT" "OUT_OF_STOCK" "DISCONTINUED"

Current product availability status

enabled
boolean
Default: true

False when the calling partner has disabled this product. Disabled products are omitted from GET /v2/products unless include_disabled=true, and are never recommended unless includeDisabled is set; GET /v2/products/{productId} always returns them.

createdAt
string <date-time>

Product creation timestamp

updatedAt
string <date-time>

Product last update timestamp

cursor
string or null (Cursor)

Pagination cursor for retrieving next/previous page; null when there are no more pages

totalHits
integer or null

Total number of results (populated only in search mode). This is the pre-filter search total and may exceed the number of items returned once products the partner has disabled are excluded; use cursor as the stop condition for pagination.

Response samples

Content type
application/json
{
  • "products": [
    ],
  • "cursor": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  • "totalHits": 0
}

Get Product Details

Retrieve detailed information for a specific product.

A product this partner has disabled is still returned with HTTP 200 and enabled: false — disabling never turns this endpoint into a 404, so historical order lines keep rendering. The 404 below means the product id does not exist.

Authorizations:
oauth2ClientCredentials
path Parameters
productId
required
integer
Example: 77123129891
query Parameters
customerId
string
Example: customerId=2QKRPxjjaxbGjOPF8TfCQb7YOAZ

Optional customer ID for customer-specific pricing. When provided, uses the customer's discount override (if set) instead of the partner's default discount.

Responses

Response Schema: application/json
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
id
integer

Unique product identifier

name
string

Product name

description
string

Detailed product description

packDescription
string

Description of the product packaging

object (PricingInfo)

Price breakdown showing partner and customer pricing.

Pricing Formula:

  • partnerPrice = MSRP × (1 - partnerDiscount%)
  • customerPrice = MSRP × (1 - customerDiscount%)
  • commission = customerPrice - partnerPrice

The customer discount comes from the partner's default discount setting, or can be overridden per-customer via customer.discountOverride.

retailPrice
required
string

Retail (MSRP) unit price (2 decimal places)

partnerPrice
required
string

What partner pays DailyStak per pill (2 decimal places)

customerPrice
required
string

What customer pays partner per pill (2 decimal places)

customerDiscount
required
integer

Customer discount percentage applied (% off MSRP); may be negative for a markup

partnerDiscount
required
integer [ 0 .. 100 ]

Partner discount percentage off MSRP (0-100%)

commission
required
string

Per-unit commission earned by the partner (customerPrice − partnerPrice, 2 decimal places); may be negative

commissionRate
required
integer

partnerDiscount − customerDiscount (percentage points of MSRP); may be negative

external
boolean

If the product is external to the pill packs

image
string <uri>

URL to the product image

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

morning
integer >= 0

Morning dosage quantity

afternoon
integer >= 0

Afternoon dosage quantity

evening
integer >= 0

Evening dosage quantity

sleep
integer >= 0

Bedtime dosage quantity

price
string or null

Retail (MSRP) unit price — the same value as pricing.retailPrice. Display it alongside your partner price (struck through, for example) to show the discount your customers receive.

form
string
Enum: "Capsule" "Veg Capsule" "Tablet" "Quick-Dissolve Tablet" "Softgel" "Powder" "Liquid" "Serum"

Physical form of the supplement (display-cased)

certifications
Array of strings

Product certifications

allergens
Array of strings

Allergy related information

categories
Array of strings

The shop categories the product belongs to, staff-assigned from a fixed list of nineteen (initially backfilled from its Shopify collections). Empty when none are set.

object (SupplementFactsPanel)

Detailed nutritional and ingredient information

servingSize
integer >= 1

Recommended serving size

pillType
string

Physical form of the supplement (display-cased)

otherIngredients
Array of strings

Additional ingredients

Array of objects (IngredientInfo)

Active ingredients with nutritional information

Array
name
string

Ingredient name

value
string or null

Amount per serving

unit
string or null

Unit of measurement

dailyValue
string or null

Percentage of recommended daily value

ingredientFamily
string or null

Roll-up slug for regimen nutrient totals; join against GET /v2/ingredient-families. Null means the row is not summable.

amountMcg
string or null

Amount per serving normalized to micrograms

amountMcgPerUnit
string or null

Normalized amount per single unit

Array of objects or null

Nested component rows for compound ingredients/blends; each row has this same shape (including ingredientFamily and amountMcg for roll-ups)

Array of objects (NutritionInfo)

Basic nutritional information

Array
name
string

Nutrient name

value
string or null

Amount per serving

unit
string or null

Unit of measurement

dailyValue
string or null

Percentage of recommended daily value

servingsPerContainer
integer or null

Number of servings per container (required for external products)

caution
string or null

Caution or warning text for the product

status
string
Enum: "ACTIVE" "INACTIVE" "DRAFT" "OUT_OF_STOCK" "DISCONTINUED"

Current product availability status

enabled
boolean
Default: true

False when the calling partner has disabled this product. Disabled products are omitted from GET /v2/products unless include_disabled=true, and are never recommended unless includeDisabled is set; GET /v2/products/{productId} always returns them.

createdAt
string <date-time>

Product creation timestamp

updatedAt
string <date-time>

Product last update timestamp

Response samples

Content type
application/json
{
  • "pricingBreakdown": {
    },
  • "id": 77123129891,
  • "name": "Elite EPA",
  • "description": "A comprehensive wellness supplement",
  • "packDescription": "Whole Body Wellness",
  • "pricing": {
    },
  • "external": false,
  • "recommendedUsage": {
    },
  • "price": "0.53",
  • "form": "Softgel",
  • "certifications": [
    ],
  • "allergens": [
    ],
  • "categories": [
    ],
  • "supplementFactsPanel": {
    },
  • "status": "ACTIVE",
  • "enabled": true,
  • "createdAt": "2023-10-01T17:02:56.866619Z",
  • "updatedAt": "2023-10-01T17:02:56.866619Z"
}

Get Product Label Image

Fetch the product's rendered label image (PNG). Responds with a 302 redirect to a presigned URL valid for 15 minutes — follow the redirect (or read the Location header) to download the PNG. Never cache or embed the presigned URL; re-request when needed.

The image has a transparent background and an even margin on every side, so it sits on your own page. Pick the text color that contrasts with that page: black (the default) for light backgrounds, white for dark ones.

A 404 means the label has not been rendered for this product in the requested text color.

Authorizations:
oauth2ClientCredentials
path Parameters
productId
required
integer
query Parameters
textColor
string
Default: "black"
Enum: "black" "white"

Text color of the label image. Case-insensitive.

Responses

Response samples

Content type
application/json
{
  • "message": "One or more requested products were not found",
  • "code": "INVALID_TEXT_COLOR",
  • "details": {
    }
}

Get AI-Powered Product Recommendations

Returns AI-powered product recommendations based on a natural language query. The query can describe conditions, symptoms, ingredients, or similar products.

Examples of queries:

  • "vitamin D for bone health"
  • "products similar to Elite EPA"
  • "magnesium for sleep support"

Matches come back ranked with confidence scores and generated reasoning. Results are non-deterministic — the same query can score differently between calls. At most 5 candidate products are considered per query regardless of limit. An empty matches array is returned both when nothing matches and when the recommendation backend is degraded.

Products this partner has disabled are excluded from the candidate set unless includeDisabled is true — a query whose only matches are disabled returns the same empty matches as a query with no matches.

Although this is a POST, it is a read-only operation: it requires only order/v2.product.read, with no write scope.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
query
required
string [ 1 .. 1000 ] characters

Natural language query describing desired products, conditions, ingredients, or similar products

limit
integer [ 1 .. 20 ]
Default: 5

Maximum number of recommendations to return. At most 5 candidates are considered per query, so values above 5 cannot return more than 5.

minConfidence
number <float> [ 0 .. 1 ]
Default: 0.6

Minimum confidence score threshold (0.0 to 1.0). Not applied when the top candidate is high-confidence enough to skip ranking.

includeDisabled
boolean
Default: false

Include products this partner has disabled. Disabled products are never recommended by default. camelCase here because it is a request-body field; the equivalent listing query parameter is include_disabled.

Responses

Response Schema: application/json
query
required
string

The original search query

required
Array of objects (ProductMatch)

List of recommended products ranked by relevance

Array
productId
required
integer

Unique product identifier

productName
required
string

Product name

score
required
number <float> [ 0 .. 1 ]

Confidence score from 0.0 to 1.0 indicating relevance to the query

reasoning
required
string

AI-generated explanation for why this product matches the query

predictivePrompts
required
Array of strings <= 2 items

Complementary product suggestions based on health correlations

message
string or null

Human-readable summary of the result

clarifications
Array of strings or null

Follow-up questions; present only when matches is empty

Request samples

Content type
application/json
Example
{
  • "query": "supplements for joint health",
  • "limit": 5,
  • "minConfidence": 0.7
}

Response samples

Content type
application/json
Example
{
  • "query": "supplements for joint health",
  • "matches": [
    ],
  • "message": "Found 1 products matching your query.",
  • "clarifications": null,
  • "predictivePrompts": [
    ]
}

List Ingredient Families

Static classification registry for the ingredientFamily slugs that appear on product supplement-facts rows. Each family carries a display name and a summable flag saying whether member amounts (amountMcg) can be meaningfully summed across products for regimen nutrient roll-ups — treat any family absent from this list, or with summable: false, as not summable.

The content is constant per deploy: responses carry Cache-Control: public, max-age=3600, and version changes only when the registry changes — use it as a cache key.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
version
required
string

Content hash of the registry; changes only when the registry changes

required
Array of objects

All known ingredient families, sorted by slug

Array
family
required
string

Slug matching ingredientFamily on product rows

displayName
required
string
summable
required
boolean

True iff summing member rows' amountMcg is meaningful

hazard
string or null
Enum: "extract-vs-whole-herb" "non-additive-units" "parent-child-overlap" "non-equivalent-forms" null

Why the family is not summable; null when summable is true

note
string or null

Practitioner-facing reason to show instead of a total

Response samples

Content type
application/json
{
  • "version": "9c47e1b02a3d",
  • "families": [
    ]
}

Shipping

Retrieve available shipping methods

Get Available Shipping Methods

Retrieve list of available shipping methods with details

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
required
Array of objects (ShippingMethod)

Your active shipping methods

Array
id
string

Use as shippingMethodId on PATCH /v2/checkout/sessions/{sessionId}/shipping

fulfillmentMethod
string or null
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Explicit fulfillment mapping; null means no configured mapping.

name
string

Shipping method name (order creation validates against these names)

description
string

Shipping method details

price
string

Shipping cost

createdAt
string <date-time>
updatedAt
string <date-time>

Response samples

Content type
application/json
{
  • "shippingMethods": [
    ]
}

Partner Settings

Manage partner account settings and branding

Get Partner Settings

Retrieve the authenticated partner's current settings including branding, contact info, and billing address

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
id
string

Unique partner identifier (read-only)

name
string

Partner name (read-only)

description
string or null
Deprecated

Always null in v2

discount
string

Human readable discount representation

discountValue
number

Numeric discount multiplier

primaryColor
string

Primary brand color (hex code)

secondaryColor
string

Secondary brand color (hex code)

logo
string or null
Deprecated

Write-only legacy field; always null in responses — use logoPrimary/logoIcon

logoPrimary
string or null

Primary logo (storage key)

logoIcon
string or null

Icon logo (storage key)

supportPhone
string or null

Support phone number

supportEmail
string <email>

Support email address

website
string <uri>

Partner website URL

webhookUrl
string or null <uri>

Legacy single webhook URL for order status notifications. Prefer managed webhook endpoints (portal) with per-endpoint secrets.

elementsCheckoutEnabled
boolean

Whether partner-hosted (Elements) checkout is enabled for this partner. Set by DailyStak; read-only here.

billingAddressLine1
string or null

Billing address line 1

billingAddressLine2
string or null

Billing address line 2

billingCity
string or null

Billing city

billingState
string or null

Billing state/province

billingPostalCode
string or null

Billing postal/zip code

billingCountry
string or null

Billing country

createdAt
string <date-time>

Partner creation timestamp

updatedAt
string <date-time>

Partner last update timestamp

Response samples

Content type
application/json
{
  • "id": "2NxK7vPqYtR5mJnLkH3wZcB",
  • "name": "Acme Health",
  • "description": "string",
  • "discount": "10%",
  • "discountValue": 0.1,
  • "primaryColor": "#1a73e8",
  • "secondaryColor": "#ffffff",
  • "logo": "string",
  • "logoPrimary": "string",
  • "logoIcon": "string",
  • "supportPhone": "555-123-4567",
  • "supportEmail": "support@partner.com",
  • "website": "https://partner.com",
  • "elementsCheckoutEnabled": false,
  • "billingAddressLine1": "123 Main St",
  • "billingAddressLine2": "Suite 100",
  • "billingCity": "San Francisco",
  • "billingState": "CA",
  • "billingPostalCode": "94102",
  • "billingCountry": "US",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Update Partner Settings

Update the authenticated partner's settings. All fields are optional — only provided fields are updated, and null values are ignored (fields cannot be cleared to null; send an empty string to overwrite). Partners cannot change their name or ID.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
primaryColor
string

Primary brand color (hex code)

secondaryColor
string

Secondary brand color (hex code)

logoPrimary
string

Primary logo (storage key)

logoIcon
string

Icon logo (storage key)

supportPhone
string

Support phone number

supportEmail
string <email>

Support email address

website
string <uri>

Partner website URL

webhookUrl
string <uri>

Webhook URL for order status notifications. Validated when set: must be https:// and resolve to a public host — loopback, private-range, and link-local addresses are rejected (422). Send an empty string to clear the URL.

billingAddressLine1
string

Billing address line 1

billingAddressLine2
string

Billing address line 2

billingCity
string

Billing city

billingState
string

Billing state/province

billingPostalCode
string

Billing postal/zip code

billingCountry
string

Billing country

Responses

Response Schema: application/json
id
string

Unique partner identifier (read-only)

name
string

Partner name (read-only)

description
string or null
Deprecated

Always null in v2

discount
string

Human readable discount representation

discountValue
number

Numeric discount multiplier

primaryColor
string

Primary brand color (hex code)

secondaryColor
string

Secondary brand color (hex code)

logo
string or null
Deprecated

Write-only legacy field; always null in responses — use logoPrimary/logoIcon

logoPrimary
string or null

Primary logo (storage key)

logoIcon
string or null

Icon logo (storage key)

supportPhone
string or null

Support phone number

supportEmail
string <email>

Support email address

website
string <uri>

Partner website URL

webhookUrl
string or null <uri>

Legacy single webhook URL for order status notifications. Prefer managed webhook endpoints (portal) with per-endpoint secrets.

elementsCheckoutEnabled
boolean

Whether partner-hosted (Elements) checkout is enabled for this partner. Set by DailyStak; read-only here.

billingAddressLine1
string or null

Billing address line 1

billingAddressLine2
string or null

Billing address line 2

billingCity
string or null

Billing city

billingState
string or null

Billing state/province

billingPostalCode
string or null

Billing postal/zip code

billingCountry
string or null

Billing country

createdAt
string <date-time>

Partner creation timestamp

updatedAt
string <date-time>

Partner last update timestamp

Request samples

Content type
application/json
{
  • "primaryColor": "#1a73e8",
  • "secondaryColor": "#ffffff",
  • "logoPrimary": "string",
  • "logoIcon": "string",
  • "supportPhone": "555-123-4567",
  • "supportEmail": "support@partner.com",
  • "website": "https://partner.com",
  • "billingAddressLine1": "123 Main St",
  • "billingAddressLine2": "Suite 100",
  • "billingCity": "San Francisco",
  • "billingState": "CA",
  • "billingPostalCode": "94102",
  • "billingCountry": "US"
}

Response samples

Content type
application/json
{
  • "id": "2NxK7vPqYtR5mJnLkH3wZcB",
  • "name": "Acme Health",
  • "description": "string",
  • "discount": "10%",
  • "discountValue": 0.1,
  • "primaryColor": "#1a73e8",
  • "secondaryColor": "#ffffff",
  • "logo": "string",
  • "logoPrimary": "string",
  • "logoIcon": "string",
  • "supportPhone": "555-123-4567",
  • "supportEmail": "support@partner.com",
  • "website": "https://partner.com",
  • "elementsCheckoutEnabled": false,
  • "billingAddressLine1": "123 Main St",
  • "billingAddressLine2": "Suite 100",
  • "billingCity": "San Francisco",
  • "billingState": "CA",
  • "billingPostalCode": "94102",
  • "billingCountry": "US",
  • "createdAt": "2023-10-01T12:00:00Z",
  • "updatedAt": "2023-10-01T12:00:00Z"
}

Get Webhook Secret

Retrieve the webhook secret used to sign deliveries to your legacy webhookUrl (the X-Juniper-Signature header). The secret is not included in the general settings response for security. You must have a webhookUrl configured to have a secret.

Managed webhook endpoints (created in the portal) each have their own per-endpoint secret shown at creation — this secret does not apply to them.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
webhookSecret
required
string

The webhook secret

Response samples

Content type
application/json
{
  • "webhookSecret": "9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e"
}

Rotate Webhook Secret

Generate a new webhook secret. The old secret becomes invalid immediately. Use this endpoint to rotate your secret if it may have been compromised.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
webhookSecret
required
string

The webhook secret

Response samples

Content type
application/json
{
  • "webhookSecret": "9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e"
}

Register Custom Domain

Register a custom domain for branded checkout pages (e.g., checkout.yourdomain.com). Returns the DNS records that must be added to your domain registrar in dnsRecords.records. How many there are depends on the domain's shape: a subdomain needs one, a root domain needs three (see CustomDomainDnsRecords).

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
domainName
required
string [ 4 .. 253 ] characters

Custom domain name (e.g., checkout.yourdomain.com)

Responses

Response Schema: application/json
id
string

Unique domain identifier

domainName
string

The custom domain name

status
string
Enum: "PENDING" "ACTIVE" "FAILED"

Current domain status

object or null

DNS records to add. Returned on registration and GET responses at any status; absent from the verify response. Read records; the two fixed slots beside it are legacy. How many records there are depends on the domain's SHAPE. A subdomain (shop.acme.com) needs ONE: a CNAME to the CDN — its certificate is CDN-managed and validated over HTTP, so there is nothing to leave in the zone forever. A root domain (acme.com, or www.acme.com — the same registration) needs THREE: the certificate-validation CNAME, a CNAME for www (canonical, serves the storefront), and an A record at the root pointing at the redirect tier's static IPs, which 301s to www. The root gets an A record because RFC 1034 forbids a CNAME at a zone apex; a provider that accepts one anyway displaces the MX records and takes the partner's email down. Each entry appears only once its value is known — the certificate-validation record is absent for the first seconds after an apex registration, because registration does not wait for the certificate authority to publish it. Re-GET to pick it up; do not re-POST (that returns 409).

Array of objects (DnsRecordEntry)

The records to create, in the order they are safe to create in (www before the root, so the root never 301s to a name that does not resolve yet).

Array
type
required
string

RR type. A at a root domain, CNAME everywhere else — a zone apex cannot hold a CNAME (RFC 1034).

name
required
string

Fully-qualified record name

zone
required
string

The zone this record belongs in (the registrable domain) — the zone file the partner actually edits

values
required
Array of strings

Every value for this record. A list because the root A record carries one entry per redirect-tier IP; all other records have exactly one.

purpose
required
string
Enum: "certificate-validation" "domain-control" "storefront" "apex-redirect"

What the record is for, for labelling. certificate-validation: proves domain control to the certificate authority. storefront: points the served host at the CDN. apex-redirect: points the root domain at the redirect tier, which 301s to www.

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

validatedAt
string or null <date-time>

When the domain was validated; null until then

createdAt
string <date-time>

When the domain was registered

Request samples

Content type
application/json
{
  • "domainName": "checkout.acme.com"
}

Response samples

Content type
application/json
{
  • "id": "2vGxyz...",
  • "domainName": "checkout.acme.com",
  • "status": "ACTIVE",
  • "dnsRecords": {
    },
  • "validatedAt": "2026-01-25T06:26:06Z",
  • "createdAt": "2026-01-25T06:17:47Z"
}

Get Custom Domain Configuration

Retrieve the current custom domain configuration including status and DNS records.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
id
string

Unique domain identifier

domainName
string

The custom domain name

status
string
Enum: "PENDING" "ACTIVE" "FAILED"

Current domain status

object or null

DNS records to add. Returned on registration and GET responses at any status; absent from the verify response. Read records; the two fixed slots beside it are legacy. How many records there are depends on the domain's SHAPE. A subdomain (shop.acme.com) needs ONE: a CNAME to the CDN — its certificate is CDN-managed and validated over HTTP, so there is nothing to leave in the zone forever. A root domain (acme.com, or www.acme.com — the same registration) needs THREE: the certificate-validation CNAME, a CNAME for www (canonical, serves the storefront), and an A record at the root pointing at the redirect tier's static IPs, which 301s to www. The root gets an A record because RFC 1034 forbids a CNAME at a zone apex; a provider that accepts one anyway displaces the MX records and takes the partner's email down. Each entry appears only once its value is known — the certificate-validation record is absent for the first seconds after an apex registration, because registration does not wait for the certificate authority to publish it. Re-GET to pick it up; do not re-POST (that returns 409).

Array of objects (DnsRecordEntry)

The records to create, in the order they are safe to create in (www before the root, so the root never 301s to a name that does not resolve yet).

Array
type
required
string

RR type. A at a root domain, CNAME everywhere else — a zone apex cannot hold a CNAME (RFC 1034).

name
required
string

Fully-qualified record name

zone
required
string

The zone this record belongs in (the registrable domain) — the zone file the partner actually edits

values
required
Array of strings

Every value for this record. A list because the root A record carries one entry per redirect-tier IP; all other records have exactly one.

purpose
required
string
Enum: "certificate-validation" "domain-control" "storefront" "apex-redirect"

What the record is for, for labelling. certificate-validation: proves domain control to the certificate authority. storefront: points the served host at the CDN. apex-redirect: points the root domain at the redirect tier, which 301s to www.

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

validatedAt
string or null <date-time>

When the domain was validated; null until then

createdAt
string <date-time>

When the domain was registered

Response samples

Content type
application/json
{
  • "id": "2vGxyz...",
  • "domainName": "checkout.acme.com",
  • "status": "ACTIVE",
  • "dnsRecords": {
    },
  • "validatedAt": "2026-01-25T06:26:06Z",
  • "createdAt": "2026-01-25T06:17:47Z"
}

Remove Custom Domain

Remove the custom domain configuration. This will:

  • Remove the domain from CloudFront
  • Delete the SSL certificate
  • Stop serving traffic on the custom domain
Authorizations:
oauth2ClientCredentials

Responses

Response samples

Content type
application/json
{
  • "message": "UNAUTHORIZED",
  • "code": "UNAUTHORIZED"
}

Verify Custom Domain

Verify the custom domain after creating every record in dnsRecords.records — one for a subdomain, three for a root domain.

Verification checks the certificate only — a domain can reach ACTIVE before the storefront or apex-redirect records are in place, so confirm your storefront loads on the domain after activation (for a root domain, check both the root and www). If still PENDING, call again after a few minutes (DNS propagation can take up to an hour). A FAILED status is terminal: delete the domain and register it again.

Authorizations:
oauth2ClientCredentials

Responses

Response Schema: application/json
id
string

Unique domain identifier

domainName
string

The custom domain name

status
string
Enum: "PENDING" "ACTIVE" "FAILED"

Current domain status

object or null

DNS records to add. Returned on registration and GET responses at any status; absent from the verify response. Read records; the two fixed slots beside it are legacy. How many records there are depends on the domain's SHAPE. A subdomain (shop.acme.com) needs ONE: a CNAME to the CDN — its certificate is CDN-managed and validated over HTTP, so there is nothing to leave in the zone forever. A root domain (acme.com, or www.acme.com — the same registration) needs THREE: the certificate-validation CNAME, a CNAME for www (canonical, serves the storefront), and an A record at the root pointing at the redirect tier's static IPs, which 301s to www. The root gets an A record because RFC 1034 forbids a CNAME at a zone apex; a provider that accepts one anyway displaces the MX records and takes the partner's email down. Each entry appears only once its value is known — the certificate-validation record is absent for the first seconds after an apex registration, because registration does not wait for the certificate authority to publish it. Re-GET to pick it up; do not re-POST (that returns 409).

Array of objects (DnsRecordEntry)

The records to create, in the order they are safe to create in (www before the root, so the root never 301s to a name that does not resolve yet).

Array
type
required
string

RR type. A at a root domain, CNAME everywhere else — a zone apex cannot hold a CNAME (RFC 1034).

name
required
string

Fully-qualified record name

zone
required
string

The zone this record belongs in (the registrable domain) — the zone file the partner actually edits

values
required
Array of strings

Every value for this record. A list because the root A record carries one entry per redirect-tier IP; all other records have exactly one.

purpose
required
string
Enum: "certificate-validation" "domain-control" "storefront" "apex-redirect"

What the record is for, for labelling. certificate-validation: proves domain control to the certificate authority. storefront: points the served host at the CDN. apex-redirect: points the root domain at the redirect tier, which 301s to www.

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

object or null
Deprecated

DEPRECATED DNS record configuration — one value, no zone. routing is populated for SUBDOMAIN registrations only, and is always null for a root domain: a single-value CNAME slot cannot express "an A record to two IPs", and the only thing that fits in it is the CNAME-at-the-apex this field used to return and no DNS provider can honour. Superseded by DnsRecordEntry in dnsRecords.records.

type
string

DNS record type

name
string

DNS record name (hostname)

value
string

DNS record value

validatedAt
string or null <date-time>

When the domain was validated; null until then

createdAt
string <date-time>

When the domain was registered

Response samples

Content type
application/json
{
  • "id": "2vGxyz...",
  • "domainName": "checkout.acme.com",
  • "status": "ACTIVE",
  • "dnsRecords": {
    },
  • "validatedAt": "2026-01-25T06:26:06Z",
  • "createdAt": "2026-01-25T06:17:47Z"
}

Amendments

Propose, review, approve, and cancel batched protocol regimen changes (amendments), with a customer-consent gate for protocols that have a live subscription

List Protocol Amendments

List all amendments on a protocol, optionally filtered by status.

The list is unpaginated and eventually consistent — an amendment created moments earlier may briefly be absent. Fetching a single amendment by ID is the read-your-write path.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
query Parameters
status
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Filter by amendment status (lowercase). Invalid values return 400.

Responses

Response Schema: application/json
required
Array of objects (Amendment)
Array
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Response samples

Content type
application/json
{
  • "amendments": [
    ]
}

Create Amendment (Draft)

Author a draft amendment: a batched set of regimen edits (add / remove / update) against the protocol. Nothing is applied to the protocol at this stage, and change payloads are not yet validated in depth — full validation happens when the amendment is sent for approval, so payload mistakes surface at send time (400), not at authoring.

Drafts do not lock regimen lines and never expire. A draft becomes actionable by calling the send endpoint, which applies the customer-consent gate.

There is no idempotency key: posting the same body twice creates two drafts.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Request Body schema: application/json
required
required
Array of objects (AmendmentChange) [ 1 .. 99 ] items

The regimen edits to propose (validated in depth at send time)

Array ([ 1 .. 99 ] items)
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Request samples

Content type
application/json
{
  • "changes": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Get Amendment

Fetch a single amendment. An amendment that exists but belongs to a different protocol (or a different partner) returns the same 404 as a missing ID.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
amendmentId
required
string

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Cancel Amendment

Cancel a draft or pending amendment. The protocol is never mutated. Resolved amendments (approved / rejected / cancelled) cannot be cancelled and return 400.

Emits the protocol_change_request.cancelled webhook event.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
amendmentId
required
string

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Send Amendment for Approval

Send a draft amendment for approval. Every change is first validation-folded against the live protocol as a dry run — an invalid payload fails here with 400 before any state changes, so a pending amendment can never be stranded un-approvable.

The customer-consent gate then decides the outcome. Integrators must branch on the returned status:

  • pending — the protocol has a live subscription and offline approval was not attested. The amendment is held for customer approval: a single-use magic-link review page is minted and a branded approval email is sent to the customer (best effort). Use the approval-link endpoint to retrieve the URL and deliver it through your own channel.
  • approved — there is no live subscription, or attestedOffline was set. The changes are folded onto the protocol immediately in this same request.

The protocol revision at send time is captured as authoredAgainstRevision and re-checked at approval.

Pending amendments held for customer approval are automatically cancelled after 45 days of inaction (a reminder email is sent to the customer after 7 days).

Emits protocol_change_request.created (and additionally protocol_change_request.approved when auto-approved).

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
amendmentId
required
string
Request Body schema: application/json
required

Send {} when not attesting offline approval.

attestedOffline
boolean
Default: false

Practitioner attests the customer's approval was already obtained offline (e.g. during an appointment). Skips the customer-consent hold; the attestation is recorded on the amendment for audit.

attestedBy
string or null

Who attested the offline approval. Defaults to the calling API client ID when omitted.

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Request samples

Content type
application/json
{
  • "attestedOffline": false,
  • "attestedBy": "string"
}

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Approve Amendment (Partner)

Approve a pending amendment on behalf of the practice, folding every change onto the protocol atomically. Each applied change bumps the protocol revision. approvedBy records the approving role (partner); customer approvals via the magic-link review page record customer.

If the protocol changed since the amendment was sent (its revision no longer matches authoredAgainstRevision), approval refuses with 409 and the protocol is untouched. Such an amendment can never be approved, so it is cancelled (cancellationReason: superseded, with the usual protocol_change_request.cancelled webhook) and its customer link stops working — re-author it against the current regimen if still needed.

Nothing is billed at approval time: future subscription renewal orders are built and priced from the updated regimen.

Emits protocol_change_request.approved.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
amendmentId
required
string

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Reject Amendment (Partner)

Reject a pending amendment on behalf of the practice. The protocol is never mutated. Emits protocol_change_request.rejected.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
amendmentId
required
string

Responses

Response Schema: application/json
id
required
string

Unique amendment identifier

protocolId
required
string

Protocol this amendment applies to

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Current lifecycle status (lowercase)

required
Array of objects (AmendmentChange)

The proposed regimen edits

Array
action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed. Required for remove/update; omit for add (ignored if sent).

object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
authoredBy
required
string

The authoring API client ID, or scheduled_change for consent amendments raised automatically by scheduled-change authoring

offlineApprovalAttested
required
boolean

Whether the practitioner attested customer approval was obtained offline

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
authoredAgainstRevision
integer or null

Protocol revision captured when the amendment was sent; null for drafts. Approval refuses with 409 if the protocol has moved past this revision.

attestedBy
string or null

Who attested offline approval

attestedAt
string or null <date-time>
approvedBy
string or null
Enum: "customer" "partner"

Approving role (not an actor ID)

approvedAt
string or null <date-time>
rejectedBy
string or null
Enum: "customer" "partner"
rejectedAt
string or null <date-time>
cancellationReason
string or null
Value: "superseded"

Set when the platform cancelled the amendment. superseded: the protocol changed after the amendment was sent, so it could no longer be approved as reviewed; send the change again.

Response samples

Content type
application/json
{
  • "id": "2xAmdK30JFgnwLrgb383PcV9Rk",
  • "protocolId": "string",
  • "status": "pending",
  • "changes": [
    ],
  • "authoredBy": "string",
  • "authoredAgainstRevision": 0,
  • "offlineApprovalAttested": true,
  • "attestedBy": "string",
  • "attestedAt": "2019-08-24T14:15:22Z",
  • "approvedBy": "customer",
  • "approvedAt": "2019-08-24T14:15:22Z",
  • "rejectedBy": "customer",
  • "rejectedAt": "2019-08-24T14:15:22Z",
  • "cancellationReason": "superseded",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Scheduled Changes

Author and manage pre-consented regimen edits that fire automatically (smart protocols)

Author a Scheduled Change

Schedule a regimen edit that fires automatically at a future order-build (smart protocols). The change is dry-run against the current regimen at authoring time, so a malformed or non-applicable edit is rejected immediately (400) rather than silently skipped at fire time.

Triggers are either order_count (fire on the Nth order) or interval (fire N days after subscription start); interval triggers require an endCondition.

Consent is derived server-side from whether the protocol has a live subscription:

  • No live subscription (pre-purchase authoring): the 201 response has origin: original_program and status: scheduled — consent is obtained at purchase and the step is fire-eligible.
  • Live subscription (post-purchase edit): the 201 response has origin: amendment and status: awaiting_consent, and a pending consent amendment is raised for customer review (find it in the protocol's amendments list; its authoredBy is scheduled_change — the protocol_change_request.created webhook fires). The step only becomes fire-eligible when the customer approves that amendment. No consent email is sent from this path — surface the approval to the customer yourself, e.g. via the amendment approval-link endpoint.

A step fires at most once: it ends applied, or skipped (with skippedReason) when the target line is missing or the add would duplicate an existing line at fire time.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
Example: 2vTgd30JFgnwLrgb383PcV9Rken
Request Body schema: application/json
required
required
object (ScheduledChangeTrigger)

When a scheduled change fires.

type
required
string
Enum: "order_count" "interval"

order_count (Nth order) or interval (N days from subscription start)

value
required
integer >= 1

Order number (for order_count) or number of days (for interval)

action
required
string
Enum: "add" "remove" "update"

The kind of edit.

targetItemId
string or null

ID of the regimen line being changed (required for remove/update).

object or null

Edit payload (camelCase keys, see RegimenChangePayload), fully validated at authoring time. Unlike amendment updates, an update payload here may NOT contain productId — author product swaps as separate remove + add steps.

productId
integer

Product to add (or swap to, amendment updates only)

quantity
integer >= 1
Default: 1

Units per day

object

Per-daypart dosing; values are units taken at that time

morning
integer >= 0
afternoon
integer >= 0
evening
integer >= 0
sleep
integer >= 0
takeWith
string
Enum: "food" "water" "empty_stomach"

Intake guidance

note
string <= 500 characters

Free-text instruction shown to the customer

reason
string

Reason for removal (remove action only)

object (ScheduledChangeEndCondition)

Bounds on when a scheduled change may fire. At least one of endDate or maxOccurrences must be set. An end condition is required whenever trigger.type is interval.

endDate
string or null <date-time>

Do not fire after this instant

maxOccurrences
integer or null >= 1

Maximum number of firings

Responses

Response Schema: application/json
id
required
string
required
object (ScheduledChangeTrigger)

When a scheduled change fires.

type
required
string
Enum: "order_count" "interval"

order_count (Nth order) or interval (N days from subscription start)

value
required
integer >= 1

Order number (for order_count) or number of days (for interval)

action
required
string
Enum: "add" "remove" "update"
origin
required
string
Enum: "original_program" "amendment"

Server-derived provenance. original_program: authored before a live subscription existed (consent obtained at purchase). amendment: authored while a subscription was live — held in awaiting_consent behind a pending consent amendment.

status
required
string
Enum: "awaiting_consent" "scheduled" "applied" "skipped" "cancelled"

awaiting_consent steps become scheduled when the customer approves the linked consent amendment (its authoredBy is scheduled_change). A fired step is terminal: applied or skipped (see skippedReason).

createdAt
required
string <date-time>
targetItemId
string or null
object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
productId
integer

Product to add (or swap to, amendment updates only)

quantity
integer >= 1
Default: 1

Units per day

object

Per-daypart dosing; values are units taken at that time

morning
integer >= 0
afternoon
integer >= 0
evening
integer >= 0
sleep
integer >= 0
takeWith
string
Enum: "food" "water" "empty_stomach"

Intake guidance

note
string <= 500 characters

Free-text instruction shown to the customer

reason
string

Reason for removal (remove action only)

object or null

Bounds on when a scheduled change may fire. At least one of endDate or maxOccurrences must be set. An end condition is required whenever trigger.type is interval.

endDate
string or null <date-time>

Do not fire after this instant

maxOccurrences
integer or null >= 1

Maximum number of firings

appliedAt
string or null <date-time>
appliedOnOrderId
string or null
skippedReason
string or null

Request samples

Content type
application/json
{
  • "trigger": {
    },
  • "action": "add",
  • "targetItemId": "string",
  • "payload": {
    },
  • "endCondition": {
    }
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "trigger": {
    },
  • "action": "add",
  • "targetItemId": "string",
  • "payload": {
    },
  • "origin": "original_program",
  • "status": "awaiting_consent",
  • "endCondition": {
    },
  • "appliedAt": "2019-08-24T14:15:22Z",
  • "appliedOnOrderId": "string",
  • "skippedReason": "string",
  • "createdAt": "2019-08-24T14:15:22Z"
}

List Scheduled Changes

List the scheduled changes on a protocol, optionally filtered by status.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
query Parameters
status
string
Enum: "awaiting_consent" "scheduled" "applied" "skipped" "cancelled"

Filter by scheduled-change status (lowercase). Invalid values return 400.

Responses

Response Schema: application/json
Array of objects (ScheduledChange)
Array
id
required
string
required
object (ScheduledChangeTrigger)

When a scheduled change fires.

action
required
string
Enum: "add" "remove" "update"
origin
required
string
Enum: "original_program" "amendment"

Server-derived provenance. original_program: authored before a live subscription existed (consent obtained at purchase). amendment: authored while a subscription was live — held in awaiting_consent behind a pending consent amendment.

status
required
string
Enum: "awaiting_consent" "scheduled" "applied" "skipped" "cancelled"

awaiting_consent steps become scheduled when the customer approves the linked consent amendment (its authoredBy is scheduled_change). A fired step is terminal: applied or skipped (see skippedReason).

createdAt
required
string <date-time>
targetItemId
string or null
object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
object or null

Bounds on when a scheduled change may fire. At least one of endDate or maxOccurrences must be set. An end condition is required whenever trigger.type is interval.

appliedAt
string or null <date-time>
appliedOnOrderId
string or null
skippedReason
string or null

Response samples

Content type
application/json
{
  • "scheduledChanges": [
    ]
}

Get Scheduled Change

Get details of a single scheduled change.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
scheduledChangeId
required
string

Responses

Response Schema: application/json
id
required
string
required
object (ScheduledChangeTrigger)

When a scheduled change fires.

type
required
string
Enum: "order_count" "interval"

order_count (Nth order) or interval (N days from subscription start)

value
required
integer >= 1

Order number (for order_count) or number of days (for interval)

action
required
string
Enum: "add" "remove" "update"
origin
required
string
Enum: "original_program" "amendment"

Server-derived provenance. original_program: authored before a live subscription existed (consent obtained at purchase). amendment: authored while a subscription was live — held in awaiting_consent behind a pending consent amendment.

status
required
string
Enum: "awaiting_consent" "scheduled" "applied" "skipped" "cancelled"

awaiting_consent steps become scheduled when the customer approves the linked consent amendment (its authoredBy is scheduled_change). A fired step is terminal: applied or skipped (see skippedReason).

createdAt
required
string <date-time>
targetItemId
string or null
object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
productId
integer

Product to add (or swap to, amendment updates only)

quantity
integer >= 1
Default: 1

Units per day

object

Per-daypart dosing; values are units taken at that time

morning
integer >= 0
afternoon
integer >= 0
evening
integer >= 0
sleep
integer >= 0
takeWith
string
Enum: "food" "water" "empty_stomach"

Intake guidance

note
string <= 500 characters

Free-text instruction shown to the customer

reason
string

Reason for removal (remove action only)

object or null

Bounds on when a scheduled change may fire. At least one of endDate or maxOccurrences must be set. An end condition is required whenever trigger.type is interval.

endDate
string or null <date-time>

Do not fire after this instant

maxOccurrences
integer or null >= 1

Maximum number of firings

appliedAt
string or null <date-time>
appliedOnOrderId
string or null
skippedReason
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "trigger": {
    },
  • "action": "add",
  • "targetItemId": "string",
  • "payload": {
    },
  • "origin": "original_program",
  • "status": "awaiting_consent",
  • "endCondition": {
    },
  • "appliedAt": "2019-08-24T14:15:22Z",
  • "appliedOnOrderId": "string",
  • "skippedReason": "string",
  • "createdAt": "2019-08-24T14:15:22Z"
}

Cancel Scheduled Change

Cancel a still-scheduled change. The cancelled change is returned. Cancelling a change that has already fired or is otherwise terminal (applied/skipped/cancelled) returns a conflict, as does an awaiting_consent step — resolve its pending consent amendment instead.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string
scheduledChangeId
required
string

Responses

Response Schema: application/json
id
required
string
required
object (ScheduledChangeTrigger)

When a scheduled change fires.

type
required
string
Enum: "order_count" "interval"

order_count (Nth order) or interval (N days from subscription start)

value
required
integer >= 1

Order number (for order_count) or number of days (for interval)

action
required
string
Enum: "add" "remove" "update"
origin
required
string
Enum: "original_program" "amendment"

Server-derived provenance. original_program: authored before a live subscription existed (consent obtained at purchase). amendment: authored while a subscription was live — held in awaiting_consent behind a pending consent amendment.

status
required
string
Enum: "awaiting_consent" "scheduled" "applied" "skipped" "cancelled"

awaiting_consent steps become scheduled when the customer approves the linked consent amendment (its authoredBy is scheduled_change). A fired step is terminal: applied or skipped (see skippedReason).

createdAt
required
string <date-time>
targetItemId
string or null
object or null (RegimenChangePayload)

Regimen edit payload. Keys are camelCase like the rest of the API; product_id and take_with are also accepted on input (a payload that sets both spellings of one key to different values is rejected). Responses and webhooks always use the camelCase keys. Other unknown keys are rejected with 400.

Allowed keys by action:

  • add — payload required; must include productId; may include quantity, schedule, takeWith, note
  • update — same keys as add. In an amendment, a productId different from the target line's swaps the line to the new product, carrying over an unspecified quantity and schedule. note and takeWith are never carried over: the new line has only the ones this payload sends. (In a scheduled change, productId is not allowed in update payloads — author swaps as separate remove + add steps.)
  • remove — payload optional; the only allowed key is reason
productId
integer

Product to add (or swap to, amendment updates only)

quantity
integer >= 1
Default: 1

Units per day

object

Per-daypart dosing; values are units taken at that time

morning
integer >= 0
afternoon
integer >= 0
evening
integer >= 0
sleep
integer >= 0
takeWith
string
Enum: "food" "water" "empty_stomach"

Intake guidance

note
string <= 500 characters

Free-text instruction shown to the customer

reason
string

Reason for removal (remove action only)

object or null

Bounds on when a scheduled change may fire. At least one of endDate or maxOccurrences must be set. An end condition is required whenever trigger.type is interval.

endDate
string or null <date-time>

Do not fire after this instant

maxOccurrences
integer or null >= 1

Maximum number of firings

appliedAt
string or null <date-time>
appliedOnOrderId
string or null
skippedReason
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "trigger": {
    },
  • "action": "add",
  • "targetItemId": "string",
  • "payload": {
    },
  • "origin": "original_program",
  • "status": "awaiting_consent",
  • "endCondition": {
    },
  • "appliedAt": "2019-08-24T14:15:22Z",
  • "appliedOnOrderId": "string",
  • "skippedReason": "string",
  • "createdAt": "2019-08-24T14:15:22Z"
}

Documents

Upload, assign, and download partner documents and DailyStak library documents

Create a Document

Register a new document and receive presigned upload targets. Upload with the returned uploadPost (an S3 form POST with size limits enforced); the plain uploadUrl PUT is deprecated. Upload targets expire after 15 minutes.

The document stays PENDING until the upload is confirmed via the confirm endpoint. Uploaded files are malware-scanned before confirmation can succeed — see the confirm endpoint.

Allowed file types: PDF, DOCX, PNG, JPEG, and WebP. Each partner may hold at most 50 documents, counting unconfirmed PENDING ones.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
name
required
string [ 1 .. 255 ] characters
fileType
required
string
Enum: "application/pdf" "application/vnd.openxmlformats-officedocument.wordprocessingml.document" "image/png" "image/jpeg" "image/webp"

MIME type of the file

originalFilename
required
string [ 1 .. 255 ] characters
description
string or null <= 1000 characters
productId
integer or null
productName
string or null

Responses

Response Schema: application/json
required
object (DocumentView)
id
required
string
partnerId
required
string
name
required
string
fileType
required
string
originalFilename
required
string
status
required
string
Enum: "PENDING" "PROCESSING" "ACTIVE" "ARCHIVED"

PENDING until the upload is confirmed; PDFs pass through PROCESSING while being stamped. ARCHIVED is reserved.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null
uploadUrl
required
string
Deprecated

Presigned URL to PUT the file to (deprecated — prefer uploadPost)

expiresIn
required
integer

Seconds until the upload targets expire (900)

object

Preferred upload mechanism: a presigned S3 form POST with the file size limit enforced server-side. Submit a multipart/form-data POST to url with every key in fields included verbatim (their keys are raw S3 form-field names), plus your file under the file key.

url
string
object
property name*
additional property
string

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "fileType": "application/pdf",
  • "originalFilename": "string",
  • "productId": 0,
  • "productName": "string"
}

Response samples

Content type
application/json
{
  • "document": {
    },
  • "uploadPost": {
    },
  • "uploadUrl": "string",
  • "expiresIn": 0
}

List Documents

Retrieve a paginated list of the partner's uploaded documents.

Authorizations:
oauth2ClientCredentials
query Parameters
limit
integer [ 1 .. 100 ]
Default: 50
cursor
string

Responses

Response Schema: application/json
required
Array of objects (DocumentView)
Array
id
required
string
partnerId
required
string
name
required
string
fileType
required
string
originalFilename
required
string
status
required
string
Enum: "PENDING" "PROCESSING" "ACTIVE" "ARCHIVED"

PENDING until the upload is confirmed; PDFs pass through PROCESSING while being stamped. ARCHIVED is reserved.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null
cursor
string or null
hasMore
boolean
Default: false

Response samples

Content type
application/json
{
  • "documents": [
    ],
  • "cursor": "string",
  • "hasMore": false
}

Get Document

Retrieve a single document's metadata.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
name
required
string
fileType
required
string
originalFilename
required
string
status
required
string
Enum: "PENDING" "PROCESSING" "ACTIVE" "ARCHIVED"

PENDING until the upload is confirmed; PDFs pass through PROCESSING while being stamped. ARCHIVED is reserved.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "partnerId": "string",
  • "name": "string",
  • "description": "string",
  • "fileType": "string",
  • "fileSizeBytes": 0,
  • "publicUrl": "string",
  • "productId": 0,
  • "productName": "string",
  • "originalFilename": "string",
  • "status": "PENDING",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Update Document

Update a document's name and/or description.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string
Request Body schema: application/json
required
name
string [ 1 .. 255 ] characters
description
string or null <= 1000 characters

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
name
required
string
fileType
required
string
originalFilename
required
string
status
required
string
Enum: "PENDING" "PROCESSING" "ACTIVE" "ARCHIVED"

PENDING until the upload is confirmed; PDFs pass through PROCESSING while being stamped. ARCHIVED is reserved.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "partnerId": "string",
  • "name": "string",
  • "description": "string",
  • "fileType": "string",
  • "fileSizeBytes": 0,
  • "publicUrl": "string",
  • "productId": 0,
  • "productName": "string",
  • "originalFilename": "string",
  • "status": "PENDING",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Delete Document

Delete a document. A document that still has active assignments cannot be deleted (409) — remove its assignments first.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response samples

Content type
application/json
{
  • "message": "One or more requested products were not found",
  • "code": "PRODUCTS_NOT_FOUND",
  • "details": {
    }
}

Confirm Document Upload

Confirm that the file has been uploaded. Non-PDF files transition PENDING → ACTIVE immediately. PDFs transition to PROCESSING while they are stamped asynchronously (typically seconds), then become ACTIVE.

A 404 here can also mean the uploaded file has not landed yet — files are malware-scanned after upload before they become confirmable. Retry after a short delay; a file that never becomes confirmable did not pass scanning.

The call is idempotent: confirming a document that is already PROCESSING or ACTIVE returns 200 with its current state and does no further work, so a lost response or a retry is safe.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
name
required
string
fileType
required
string
originalFilename
required
string
status
required
string
Enum: "PENDING" "PROCESSING" "ACTIVE" "ARCHIVED"

PENDING until the upload is confirmed; PDFs pass through PROCESSING while being stamped. ARCHIVED is reserved.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "partnerId": "string",
  • "name": "string",
  • "description": "string",
  • "fileType": "string",
  • "fileSizeBytes": 0,
  • "publicUrl": "string",
  • "productId": 0,
  • "productName": "string",
  • "originalFilename": "string",
  • "status": "PENDING",
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Download Document

Get a download URL for the document file, valid for one hour. For stamped PDFs the stamped copy is served. There is no status gate — a still-PENDING document yields a URL whose file may not exist yet.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
downloadUrl
required
string

Download URL — a permanent CDN URL when available, otherwise a presigned URL valid for one hour

Response samples

Content type
application/json
{
  • "downloadUrl": "string"
}

Assign Document

Assign an active document to a scope (account-wide, a protocol, or a protocol template) with a delivery mode (digital and/or print).

scopeId is required in practice for PROTOCOL and PROTOCOL_TEMPLATE scopes — omitting it silently records the assignment against the account instead of erroring. The target's existence is not validated, and duplicate assignments are allowed.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string
Request Body schema: application/json
required
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
scopeId
string or null

Protocol or template ID — required in practice for PROTOCOL and PROTOCOL_TEMPLATE scopes (omitting it silently records the assignment against the account). Omit for ACCOUNT scope.

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
documentId
required
string
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
scopeId
required
string
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
printFulfilled
required
boolean
createdAt
required
string <date-time>

Request samples

Content type
application/json
{
  • "scope": "ACCOUNT",
  • "scopeId": "string",
  • "deliveryMode": "DIGITAL_ONLY"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "partnerId": "string",
  • "documentId": "string",
  • "scope": "ACCOUNT",
  • "scopeId": "string",
  • "deliveryMode": "DIGITAL_ONLY",
  • "printFulfilled": true,
  • "createdAt": "2019-08-24T14:15:22Z"
}

List Document Assignments

List all assignments for a document.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
required
Array of objects (AssignmentView)
Array
id
required
string
partnerId
required
string
documentId
required
string
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
scopeId
required
string
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
printFulfilled
required
boolean
createdAt
required
string <date-time>

Response samples

Content type
application/json
{
  • "assignments": [
    ]
}

Update Document Assignment

Update the delivery mode of an existing assignment. printFulfilled is preserved and cannot be reset.

Authorizations:
oauth2ClientCredentials
path Parameters
assignmentId
required
string
Request Body schema: application/json
required
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"

Responses

Response Schema: application/json
id
required
string
partnerId
required
string
documentId
required
string
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
scopeId
required
string
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
printFulfilled
required
boolean
createdAt
required
string <date-time>

Request samples

Content type
application/json
{
  • "deliveryMode": "DIGITAL_ONLY"
}

Response samples

Content type
application/json
{
  • "id": "string",
  • "partnerId": "string",
  • "documentId": "string",
  • "scope": "ACCOUNT",
  • "scopeId": "string",
  • "deliveryMode": "DIGITAL_ONLY",
  • "printFulfilled": true,
  • "createdAt": "2019-08-24T14:15:22Z"
}

Delete Document Assignment

Remove a document assignment.

Authorizations:
oauth2ClientCredentials
path Parameters
assignmentId
required
string

Responses

Response samples

Content type
application/json
{
  • "message": "One or more requested products were not found",
  • "code": "PRODUCTS_NOT_FOUND",
  • "details": {
    }
}

List Protocol Documents

List the document assignments visible for a protocol: the union of account-wide assignments and assignments targeting this specific protocol. An unknown protocol ID is not an error — it returns just the account-wide assignments.

Authorizations:
oauth2ClientCredentials
path Parameters
protocolId
required
string

Responses

Response Schema: application/json
required
Array of objects (AssignmentView)
Array
id
required
string
partnerId
required
string
documentId
required
string
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
scopeId
required
string
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
printFulfilled
required
boolean
createdAt
required
string <date-time>

Response samples

Content type
application/json
{
  • "assignments": [
    ]
}

List Protocol Template Documents

List the documents attached to one of your protocol templates: its PROTOCOL_TEMPLATE assignments, one per document attached in the portal. Account-wide assignments are not included. The template's own documentIds field does not reflect these, so use this route to find a template's documents. An unknown template ID is not an error: it returns an empty list.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string

Responses

Response Schema: application/json
required
Array of objects (AssignmentView)
Array
id
required
string
partnerId
required
string
documentId
required
string
scope
required
string
Enum: "ACCOUNT" "PROTOCOL" "PROTOCOL_TEMPLATE"
scopeId
required
string
deliveryMode
required
string
Enum: "DIGITAL_ONLY" "DIGITAL_AND_PRINT" "DIGITAL_AND_ONE_TIME_PRINT"
printFulfilled
required
boolean
createdAt
required
string <date-time>

Response samples

Content type
application/json
{
  • "assignments": [
    ]
}

List Library Documents

List DailyStak-provided library documents available to all partners. Supports an optional productId filter to find documents attached to a specific product; the filtered mode returns all matches in one response (limit/cursor are ignored and cursor is always null).

Authorizations:
oauth2ClientCredentials
query Parameters
productId
integer

Filter library documents by associated product ID (unpaginated mode)

limit
integer [ 1 .. 100 ]
Default: 50
cursor
string

Responses

Response Schema: application/json
required
Array of objects (LibraryDocumentView)
Array
id
required
string
name
required
string
fileType
required
string
status
required
string
createdAt
required
string
updatedAt
required
string
description
string or null
category
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null
cursor
string or null
hasMore
boolean
Default: false

Response samples

Content type
application/json
{
  • "documents": [
    ],
  • "cursor": "string",
  • "hasMore": false
}

Get Library Document

Retrieve a single DailyStak library document's metadata.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
id
required
string
name
required
string
fileType
required
string
status
required
string
createdAt
required
string
updatedAt
required
string
description
string or null
category
string or null
fileSizeBytes
integer or null
publicUrl
string or null
productId
integer or null
productName
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "description": "string",
  • "category": "string",
  • "fileType": "string",
  • "fileSizeBytes": 0,
  • "publicUrl": "string",
  • "productId": 0,
  • "productName": "string",
  • "status": "string",
  • "createdAt": "string",
  • "updatedAt": "string"
}

Download Library Document

Get a download URL for a library document. This is usually a permanent CDN URL; when none exists a presigned URL valid for one hour is returned instead — don't cache the URL long-term.

Authorizations:
oauth2ClientCredentials
path Parameters
documentId
required
string

Responses

Response Schema: application/json
downloadUrl
required
string

Download URL — a permanent CDN URL when available, otherwise a presigned URL valid for one hour

Response samples

Content type
application/json
{
  • "downloadUrl": "string"
}

Protocol Templates

Create and manage your own reusable protocol templates (the partner-owned counterpart to the read-only DailyStak library)

List Protocol Templates

List all of your active protocol templates, optionally filtered by a case-insensitive substring search over template names, line-item product names and tags.

Templates created here and templates copied from the DailyStak library (POST /v2/library-templates/{templateId}/copy) both appear. Deleted templates are excluded. The list is unpaginated.

Authorizations:
oauth2ClientCredentials
query Parameters
search
string
Example: search=probiotic

Case-insensitive substring filter over template name, line-item product names and tags

Responses

Response Schema: application/json
required
Array of objects (ProtocolTemplate)
Array
id
required
string

Server-generated template identifier

name
required
string

Template display name

description
required
string or null
documentIds
required
Array of strings

Attached document IDs. Populated only by the library-copy flow; reset to [] by any update. Cannot be set through this surface.

sourceTemplateId
required
string or null

DailyStak library template this was copied from; null for templates created directly. Reset to null by any update.

image
required
string or null <uri>

The https image URL set on the template, or null.

tags
required
Array of strings

Tags, stored normalized (trimmed, inner whitespace collapsed, lower-cased, de-duplicated). The list search filter also matches them. [] when none.

required
Array of objects (ProtocolTemplateLineItem)
createdAt
required
string <date-time>
updatedAt
required
string <date-time>

Response samples

Content type
application/json
{
  • "templates": [
    ]
}

Create Protocol Template

Create a reusable, named group of product line items to apply when building protocols for customers.

Line items are enriched server-side from the product catalog: name, image, and external are always taken from the product record, never from the request, and packDescription falls back to the product's own pack description when not supplied. The enrichment is a snapshot at write time — later catalog changes do not propagate into stored templates.

Every referenced product must exist and be orderable; unknown or discontinued/draft products reject the whole request with 400.

There is no idempotency key: retrying a successful create makes a duplicate template.

Authorizations:
oauth2ClientCredentials
Request Body schema: application/json
required
name
required
string <= 100 characters
required
Array of objects (ProtocolTemplateLineItemInput) [ 1 .. 99 ] items

Products in the template (duplicate product IDs are allowed)

Array ([ 1 .. 99 ] items)
productId
required
integer

Must reference an existing, orderable catalog product

quantity
required
integer
object or null

Dosing schedule: all keys optional, defaulting to 0

packDescription
string or null

Optional override for the product's pack description

note
string or null <= 500 characters
description
string or null <= 500 characters
image
string or null <= 2048 characters

Use the URL of a file from your DailyStak media library. Only the scheme and host are checked, not whether the file exists or who uploaded it: it must be https on the DailyStak media host, with no credentials, whitespace or control characters. Omitted, null or blank means none. A URL that fails these checks is refused with 422 INVALID_TEMPLATE_FIELD; a value over 2048 characters or of the wrong type gets 422 VALIDATION_FAILED.

tags
Array of strings or null <= 50 items

Tags. Each is trimmed, has inner whitespace collapsed and is lower-cased; blanks and duplicates are dropped, keeping the first occurrence. After that, each tag may be at most 30 characters and at most 10 may remain, or the request is refused with 422 INVALID_TEMPLATE_FIELD. Omitted or null means none.

Responses

Response Schema: application/json
id
required
string

Server-generated template identifier

name
required
string

Template display name

description
required
string or null
documentIds
required
Array of strings

Attached document IDs. Populated only by the library-copy flow; reset to [] by any update. Cannot be set through this surface.

sourceTemplateId
required
string or null

DailyStak library template this was copied from; null for templates created directly. Reset to null by any update.

image
required
string or null <uri>

The https image URL set on the template, or null.

tags
required
Array of strings

Tags, stored normalized (trimmed, inner whitespace collapsed, lower-cased, de-duplicated). The list search filter also matches them. [] when none.

required
Array of objects (ProtocolTemplateLineItem)
Array
productId
required
integer

Catalog product ID

quantity
required
integer
name
string or null

Product name (catalog snapshot)

image
string or null

Product image URL (catalog snapshot)

object or null

Dosing schedule; all four keys present when non-null

packDescription
string or null

Request override, or the product's own pack description

external
boolean

Whether the product is an external (non-pack) product

note
string or null <= 500 characters
createdAt
required
string <date-time>
updatedAt
required
string <date-time>

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "lineItems": [
    ],
  • "image": "string",
  • "tags": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "2tHkQxL0p9dY6fT3vNqW8rZaBc4",
  • "name": "string",
  • "description": "string",
  • "documentIds": [
    ],
  • "sourceTemplateId": "string",
  • "image": "http://example.com",
  • "tags": [
    ],
  • "lineItems": [
    ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Get Protocol Template

Fetch one of your templates by ID. Another partner's template is indistinguishable from a missing one (404).

Note: a deleted template no longer appears in the list but remains readable by ID.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string

Responses

Response Schema: application/json
id
required
string

Server-generated template identifier

name
required
string

Template display name

description
required
string or null
documentIds
required
Array of strings

Attached document IDs. Populated only by the library-copy flow; reset to [] by any update. Cannot be set through this surface.

sourceTemplateId
required
string or null

DailyStak library template this was copied from; null for templates created directly. Reset to null by any update.

image
required
string or null <uri>

The https image URL set on the template, or null.

tags
required
Array of strings

Tags, stored normalized (trimmed, inner whitespace collapsed, lower-cased, de-duplicated). The list search filter also matches them. [] when none.

required
Array of objects (ProtocolTemplateLineItem)
Array
productId
required
integer

Catalog product ID

quantity
required
integer
name
string or null

Product name (catalog snapshot)

image
string or null

Product image URL (catalog snapshot)

object or null

Dosing schedule; all four keys present when non-null

packDescription
string or null

Request override, or the product's own pack description

external
boolean

Whether the product is an external (non-pack) product

note
string or null <= 500 characters
createdAt
required
string <date-time>
updatedAt
required
string <date-time>

Response samples

Content type
application/json
{
  • "id": "2tHkQxL0p9dY6fT3vNqW8rZaBc4",
  • "name": "string",
  • "description": "string",
  • "documentIds": [
    ],
  • "sourceTemplateId": "string",
  • "image": "http://example.com",
  • "tags": [
    ],
  • "lineItems": [
    ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Update Protocol Template

Partially update a template. Omitted (or explicitly null) fields are preserved — description cannot be cleared back to null. image: "" and tags: [] clear those fields. lineItems, when supplied, replace the entire set and go through the same catalog validation and enrichment as create.

Warning: every update resets documentIds to [] and sourceTemplateId to null — updating a template copied from the DailyStak library severs its library lineage and document attachments.

There is no optimistic concurrency control; the last write wins.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string
Request Body schema: application/json
required
name
string or null <= 100 characters
description
string or null <= 500 characters

New description; cannot be cleared back to null

Array of objects or null (ProtocolTemplateLineItemInput) [ 1 .. 99 ] items
Array ([ 1 .. 99 ] items)
productId
required
integer

Must reference an existing, orderable catalog product

quantity
required
integer
object or null

Dosing schedule: all keys optional, defaulting to 0

packDescription
string or null

Optional override for the product's pack description

note
string or null <= 500 characters
image
string or null <= 2048 characters

New template image, under the same rules as create. Omitted or null keeps the current image; "" clears it.

tags
Array of strings or null <= 50 items

New tags, normalized and capped as on create (each at most 30 characters once normalized, at most 10 after de-duplication). They replace the current set wholesale. Omitted or null keeps the current tags; [] clears them.

Responses

Response Schema: application/json
id
required
string

Server-generated template identifier

name
required
string

Template display name

description
required
string or null
documentIds
required
Array of strings

Attached document IDs. Populated only by the library-copy flow; reset to [] by any update. Cannot be set through this surface.

sourceTemplateId
required
string or null

DailyStak library template this was copied from; null for templates created directly. Reset to null by any update.

image
required
string or null <uri>

The https image URL set on the template, or null.

tags
required
Array of strings

Tags, stored normalized (trimmed, inner whitespace collapsed, lower-cased, de-duplicated). The list search filter also matches them. [] when none.

required
Array of objects (ProtocolTemplateLineItem)
Array
productId
required
integer

Catalog product ID

quantity
required
integer
name
string or null

Product name (catalog snapshot)

image
string or null

Product image URL (catalog snapshot)

object or null

Dosing schedule; all four keys present when non-null

packDescription
string or null

Request override, or the product's own pack description

external
boolean

Whether the product is an external (non-pack) product

note
string or null <= 500 characters
createdAt
required
string <date-time>
updatedAt
required
string <date-time>

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "lineItems": [
    ],
  • "image": "string",
  • "tags": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "2tHkQxL0p9dY6fT3vNqW8rZaBc4",
  • "name": "string",
  • "description": "string",
  • "documentIds": [
    ],
  • "sourceTemplateId": "string",
  • "image": "http://example.com",
  • "tags": [
    ],
  • "lineItems": [
    ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Delete Protocol Template

Soft-delete a template: it drops out of the template list. There is no undelete. Repeating the delete returns 200 again.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string

Responses

Response Schema: application/json
templateId
string

ID of the deleted template

message
string

Response samples

Content type
application/json
{
  • "templateId": "string",
  • "message": "Protocol template deleted successfully"
}

Library Templates

Browse and copy DailyStak-curated protocol library templates

List Library Templates

List DailyStak-curated protocol library templates available to copy into the partner's own protocol templates. The full active catalog is returned in one response (no pagination).

Authorizations:
oauth2ClientCredentials
query Parameters
category
string

Filter templates by category (exact, case-sensitive match)

search
string

Case-insensitive substring match over template names and line-item product names

Responses

Response Schema: application/json
required
Array of objects (LibraryTemplateView)
Array
id
required
string
name
required
string
category
required
string
required
Array of objects (LibraryTemplateLineItem)
createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
documentIds
Array of strings

Response samples

Content type
application/json
{
  • "templates": [
    ]
}

Get Library Template

Retrieve a single library template with its line items.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string

Responses

Response Schema: application/json
id
required
string
name
required
string
category
required
string
required
Array of objects (LibraryTemplateLineItem)
Array
productId
required
integer
quantity
required
integer
name
string or null
image
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
external
boolean
Default: false
note
string or null
createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
documentIds
Array of strings

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "description": "string",
  • "category": "string",
  • "documentIds": [
    ],
  • "lineItems": [
    ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Copy Library Template

Copy a DailyStak library template into the partner's own protocol templates. The newly-created template is returned (the copy response itself does not include documentIds); it is thereafter readable and manageable at /v2/protocol-templates, where it carries sourceTemplateId (the library template it came from) and the library template's documentIds.

Authorizations:
oauth2ClientCredentials
path Parameters
templateId
required
string

Responses

Response Schema: application/json
id
required
string
name
required
string
image
required
string or null <uri>

The https image URL set on the template, or null. A fresh copy always returns null.

tags
required
Array of strings

Tags, stored normalized (trimmed, inner whitespace collapsed, lower-cased, de-duplicated). A fresh copy always returns [].

required
Array of objects (LibraryTemplateLineItem)
Array
productId
required
integer
quantity
required
integer
name
string or null
image
string or null
object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
external
boolean
Default: false
note
string or null
createdAt
required
string <date-time>
updatedAt
required
string <date-time>
description
string or null
sourceTemplateId
string or null

Response samples

Content type
application/json
{
  • "id": "string",
  • "name": "string",
  • "description": "string",
  • "sourceTemplateId": "string",
  • "image": "http://example.com",
  • "tags": [ ],
  • "lineItems": [
    ],
  • "createdAt": "2019-08-24T14:15:22Z",
  • "updatedAt": "2019-08-24T14:15:22Z"
}

Webhooks

Signed event deliveries sent from DailyStak to your HTTPS endpoint. Endpoints are managed in the partner portal, not through this API

order.created Webhook

An order was created — by POST /v2/orders, by a completed checkout session, or by a subscription renewal. Fired off the order record's creation, so it is the single signal for all three paths.

fulfillmentInfo is null here; checkoutSessionId and metadata correlate the order back to the checkout that produced it.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.updated Webhook

Any change to the order that does not map to a more specific event: progress through PROCESSING, DISPENSING, FILLED and AWAITING_SHIPMENT, shipping-address edits, and tracking updates.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.cancelled Webhook

The order was cancelled — through the API, in the portal, or by the cascade from deleting a customer.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.shipped Webhook

The carrier accepted the shipment. fulfillmentInfo now carries carrier, tracking number and tracking URL.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.delivered Webhook

Carrier tracking reported delivery.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.exception Webhook

Fulfillment hit a problem that needs attention. Orders can recover from this state and continue.

All order.* events carry the same payload; only type differs.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

order.fulfilled Webhook

The order passed through FULFILLED on its way to SHIPPED. The packing flow writes that transition as its own step, so this fires immediately before order.shipped.

Not every shipment emits it. An order advanced by the carrier ship-notify goes from FILLED straight to SHIPPED and never touches FULFILLED, so treat this as an optional signal and key fulfillment logic off order.shipped.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "order.created" "order.updated" "order.cancelled" "order.fulfilled" "order.shipped" "order.delivered" "order.exception"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "order.created",
  • "created": 1753452000,
  • "data": {
    }
}

legacyOrderWebhook Webhook

Legacy shape — not an event type. Deliveries to a partner-level webhookUrl (Partner Settings) are not wrapped in the event envelope: the body is {"order": {...}} with no id, type or created, and fires on the same order changes as the order.* events above.

Signed with the partner-level secret from GET /v2/partners/settings/webhook-secret. If no secret is set the delivery goes out unsigned, with no X-Juniper-Signature header at all.

Configure managed endpoints in the portal instead — they get the envelope, per-endpoint secrets, event filtering and delivery history. If you have both, every order change is delivered to both.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
required
object (OrderWebhookPayload)

Shared by every order.* event — there are no per-event fields. Deliberately thin: fetch GET /v2/orders/{orderId} for the full order.

orderId
required
string
status
required
string
Enum: "NEW" "PROCESSING" "DISPENSING" "FILLED" "FULFILLED" "AWAITING_SHIPMENT" "SHIPPED" "DELIVERED" "EXCEPTION" "CANCELLED"

The order's status at the moment the event fired. The status is what selects the event type: NEW on insert fires order.created, and thereafter CANCELLED, FULFILLED, SHIPPED, DELIVERED and EXCEPTION fire their matching events while every other status fires order.updated.

required
object (FulfillmentInfo)

Details about order fulfillment and shipping. All fields are nullable and populate as fulfillment progresses.

carrier
string or null

Shipping carrier name

trackingNumber
string or null

Shipping carrier tracking number

trackingStatus
string or null

Current tracking status from carrier

trackingUrl
string or null <uri>

URL to track the shipment

status
string or null

Fulfillment status

serviceCode
string or null

Carrier service code

createdAt
string or null <date-time>
Array of objects (TrackingCheckpoint)

Tracking checkpoint history

createdAt
required
string

ISO 8601. Carries a +00:00 offset; orders created before 2026-03 are stored as naive timestamps and serialize without one.

updatedAt
required
string
checkoutSessionId
string or null

The checkout session this order was built from; null for orders created any other way (API, renewal, Shopify). Present on every order.* event, not only order.created.

object or null

Metadata carried through from the checkout session. Numbers arrive as strings (3 comes back as "3"), including nested ones and those inside arrays; strings, booleans, null and the object/array structure are preserved.

property name*
additional property
any
subscriptionId
string or null

The subscription that generated this order; null for one-time and API orders. Present on every order.* event, not only order.created, so a renewal can be attributed without a read.

protocolId
string or null

The protocol this order was built from; null for API orders. Present on every order.* event.

Responses

Request samples

Content type
application/json
{
  • "order": {
    }
}

customer.created Webhook

POST /v2/customers succeeded.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "customer.created" "customer.updated" "customer.deleted"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

customerId
required
string

Note the name — the REST Customer resource calls this id.

sourceId
required
string or null

Your own identifier for the customer.

firstName
required
string
lastName
required
string
email
required
string or null
phone
required
string or null
managedBy
required
string or null

Caregiver customer id for a managed customer; null otherwise.

payer
required
string
Enum: "customer" "practice"

Who pays for this customer's orders; see Customer.payer.

createdAt
required
string or null
updatedAt
required
string or null

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "customer.created",
  • "created": 1753452000,
  • "data": {
    }
}

customer.updated Webhook

PUT /v2/customers/{customerId} succeeded.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "customer.created" "customer.updated" "customer.deleted"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

customerId
required
string

Note the name — the REST Customer resource calls this id.

sourceId
required
string or null

Your own identifier for the customer.

firstName
required
string
lastName
required
string
email
required
string or null
phone
required
string or null
managedBy
required
string or null

Caregiver customer id for a managed customer; null otherwise.

payer
required
string
Enum: "customer" "practice"

Who pays for this customer's orders; see Customer.payer.

createdAt
required
string or null
updatedAt
required
string or null

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "customer.created",
  • "created": 1753452000,
  • "data": {
    }
}

customer.deleted Webhook

DELETE /v2/customers/{customerId} succeeded. The payload is the customer's pre-delete state. The delete cascade also emits order.cancelled for each NEW order it cancels, but emits no subscription events.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "customer.created" "customer.updated" "customer.deleted"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

customerId
required
string

Note the name — the REST Customer resource calls this id.

sourceId
required
string or null

Your own identifier for the customer.

firstName
required
string
lastName
required
string
email
required
string or null
phone
required
string or null
managedBy
required
string or null

Caregiver customer id for a managed customer; null otherwise.

payer
required
string
Enum: "customer" "practice"

Who pays for this customer's orders; see Customer.payer.

createdAt
required
string or null
updatedAt
required
string or null

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "customer.created",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.created Webhook

A subscription was created — through POST /v2/subscriptions, by your team in the portal, or by a completed hosted checkout. Carries protocolId and customerId; with one protocol per cart, protocolId is your cart correlation key.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "subscription.created"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.created",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.updated Webhook

daysSupply, shippingMethod, endDate or notes changed, or a product was added or removed by the customer in your hosted storefront. One payload either way; initiatedBy says which surface and changedFields which fields, with reason cadence_changed when daysSupply moved and fields_changed otherwise. A product change adds action and productId instead.

Changes you make through the API emit this too.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.paused Webhook

The subscription moved to PAUSED — through the API, the portal, or the customer in your storefront. One payload either way, with changedBy naming the actor and initiatedBy the surface.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.resumed Webhook

A PAUSED subscription returned to ACTIVE — through the API, the portal, or the customer in your storefront.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.cancelled Webhook

The subscription moved to CANCELLED, whoever caused it. reason says why: requested, dunning_exhausted, customer_deleted (the customer-delete cascade), protocol_deactivated, checkout_compensation, or end_date_reached.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.skipped Webhook

A cycle was skipped and the billing date advanced — through the API, the portal, or the customer in your storefront.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.rescheduled Webhook

The next billing date moved — through the API, the portal, or the customer in your storefront.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.renewal_pending Webhook

A partner-managed renewal came due and the subscription entered AWAITING_PARTNER. Collect payment yourself, then call POST /v2/subscriptions/{subscriptionId}/confirm-renewal with the confirmationToken — which expires 48 hours after issuance.

Miss three consecutive windows and the subscription emits subscription.payment_failed with reason: "partner_confirmation_timeout".

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "subscription.renewal_pending"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

subscriptionId
required
string
customerId
required
string
protocolId
required
string
confirmationToken
required
string

Pass to POST /v2/subscriptions/{subscriptionId}/confirm-renewal. Expires 48 hours after issuance.

expiresAt
required
string
amountCents
required
integer

Integer cents to collect.

required
Array of objects
Array
productId
string
name
string or null

Null on regimen-priced protocols, which carry no denormalized name.

quantity
integer
customerPrice
string or null

Decimal string; null on regimen-priced protocols.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.renewal_pending",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.renewal_blocked Webhook

A platform-billed renewal came due but can't be charged, and won't be until the cause is fixed: no saved payment method, no payment profile (or one that doesn't exist in the current payment account), no shipping address, a zero total, or a missing customer or protocol. Nothing was charged and the subscription's status is unchanged; it is retried on each later billing run and charges once the cause is fixed. The subscription carries renewalBlockedReason until then.

Sent once per cause per billing cycle, from the scheduled billing run (a portal "bill now" that can't charge does not send it). A transient payment provider error is retried automatically and never sends this event.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "subscription.renewal_blocked"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

subscriptionId
required
string
reason
required
string
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found"
message
required
string

Human-readable explanation of reason.

customerId
string
protocolId
string
nextBillingDate
string or null

The billing date of the renewal that was not charged.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.renewal_blocked",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.order_created Webhook

A renewal generated an order. This is the renewal signal — there is no subscription.renewed event.

Platform-billed renewals, renewals you confirm, and portal "bill now" all send the same payload: confirmedBy and billedNow say which path produced it. The order itself also emits its own order.created.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "subscription.order_created"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

subscriptionId
required
string
orderId
required
string
confirmedBy
required
string
Enum: "platform" "partner"

platform when a platform-billed renewal settled; partner when you confirmed the renewal or billed it from the portal.

billedNow
required
boolean

Whether the platform took payment for this renewal. False means you collected it yourself — see externalPaymentReference.

customerId
string or null
protocolId
string or null
amountCharged
integer or null

Integer cents. Null on partner-collected renewals.

nextBillingDate
string or null
externalPaymentReference
string or null

Your reference for the payment you collected, when you supplied one.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.order_created",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.payment_recovered Webhook

A dunning retry settled the open renewal invoice and the subscription returned from PAST_DUE to ACTIVE. reason is retry_succeeded.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "subscription.updated" "subscription.paused" "subscription.resumed" "subscription.cancelled" "subscription.skipped" "subscription.rescheduled" "subscription.payment_recovered"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionLifecyclePayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

changedBy
string or null

The raw actor — partner:{clientId} for an API call, the partner user's email for a change made in the portal, the customer's id for one they made in your hosted storefront, or a platform name such as billing or dunning. Null when the actor is unknown.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Which surface caused the change. A record written before this field existed reports PLATFORM.

reason
string or null
Enum: "requested" "dunning_exhausted" "customer_deleted" "protocol_deactivated" "checkout_compensation" "end_date_reached" "cadence_changed" "fields_changed" "retry_succeeded"

Why, where the event has a reason to give. Null otherwise. subscription.cancelled: requested, dunning_exhausted, customer_deleted, protocol_deactivated, checkout_compensation, end_date_reached. subscription.updated: cadence_changed or fields_changed. subscription.payment_recovered: retry_succeeded.

changedFields
Array of strings
Items Enum: "daysSupply" "shippingMethod" "endDate" "notes"

Present on subscription.updated: the record fields this write changed.

action
string
Enum: "product_added" "product_removed"

Present only on subscription.updated raised by a customer adding or removing a product in your hosted storefront.

productId
string

The product added or removed, alongside action.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.updated",
  • "created": 1753452000,
  • "data": {
    }
}

subscription.payment_failed Webhook

A renewal payment attempt failed. EVERY Juniper-managed dunning attempt sends this — the first decline, each 3-day retry, and the terminal one, which subscription.cancelled with reason: dunning_exhausted follows — as does a third consecutive missed partner-managed confirmation window.

One payload for all causes: the subscription record plus why it failed and when it will be retried. nextRetryAt is null once the retry budget is spent.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "subscription.payment_failed"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (SubscriptionPaymentFailedPayload)

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
protocolId
required
string

Linked protocol that defines the regimen and customer

customerId
required
string

Customer this subscription bills; always the linked protocol's customer

daysSupply
required
integer [ 1 .. 365 ]

Renewal cadence in days, and the days of product each renewal order supplies. Set at creation; change it with PUT /v2/subscriptions/{subscriptionId}.

shippingMethod
required
string
Enum: "STANDARD" "EXPEDITED" "OVERNIGHT"

Shipping method applied to renewal orders

status
required
string
Enum: "PENDING" "ACTIVE" "PAST_DUE" "PAUSED" "CANCELLED" "FAILED" "AWAITING_PARTNER"

Lifecycle status. PENDING appears only for checkout-created subscriptions awaiting purchase. PAST_DUE is the Juniper-managed dunning window after a failed renewal charge. AWAITING_PARTNER means a partner-managed renewal is waiting for confirm-renewal. FAILED follows three consecutive missed partner renewal confirmations. CANCELLED is terminal.

createdAt
required
string <date-time>
updatedAt
required
string <date-time>
totalOrdersGenerated
required
integer
required
Array of objects (ScheduleChange)

Audit trail of schedule changes

Array
changeType
required
string
Enum: "SKIP" "RESCHEDULE" "ORDER_GENERATED" "PAUSE" "RESUME" "CANCEL"
changedAt
required
string <date-time>
previousDate
string or null <date-time>
newDate
string or null <date-time>
reason
string or null
changedBy
string or null

Actor, when recorded — e.g. partner:<clientId> for skip and reschedule, dunning for cancellation after exhausted payment retries

isActive
required
boolean

status is ACTIVE and endDate (if set) has not been reached

isDueForBilling
required
boolean

Active and nextBillingDate is now or earlier

paymentFailureCount
required
integer
reason
required
string
Enum: "card_declined" "authentication_required" "partner_confirmation_timeout"

Why the attempt failed. card_declined and authentication_required are Juniper-managed renewal charges; partner_confirmation_timeout is a third consecutive missed partner-managed confirmation window.

attempt
required
integer

Which attempt this was, counting from 1.

maxAttempts
required
integer

The retry budget. On the last attempt the subscription is terminal — CANCELLED for a Juniper-managed subscription (followed by subscription.cancelled with reason: dunning_exhausted), FAILED for a partner-managed one.

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
object (EmbeddedProtocol)

Protocol summary embedded in the single-subscription GET response

id
string
customerId
string
status
string
Array of objects

The protocol's line items (camelCase keys)

createdAt
string <date-time>
updatedAt
string <date-time>
nextBillingDate
string or null <date-time>

Next renewal instant; renewals fire on the scheduler tick at or after this time

startDate
string or null <date-time>
endDate
string or null <date-time>

From this instant on (inclusive) no renewal is charged or flagged: when its next renewal comes due, the subscription is cancelled (scheduleHistory reason "End date reached") and subscription.cancelled is sent

lastOrderId
string or null

Most recently generated order

notes
string or null
lastPaymentError
string or null
renewalBlockedReason
string or null
Enum: "customer_not_found" "no_provider_customer" "provider_customer_missing" "no_payment_method" "no_shipping_address" "zero_total" "protocol_not_found" null

Set when a due platform-billed renewal can't be charged until its cause is fixed; null otherwise. Cleared when a renewal is charged, or the subscription is paused or cancelled. See subscription.renewal_blocked.

renewalBlockedAt
string or null <date-time>
renewalBlockedMessage
string or null

Human-readable explanation of renewalBlockedReason.

cadenceTermsAcceptedAt
string or null <date-time>

When you asserted, via termsAcceptedAt on PUT, that the customer accepted a cadence change and its new recurring amount. Read back so you can verify what we recorded. Null if the cadence has never been changed.

payer
string or null
Enum: "customer" "practice"

Who is charged on each renewal, frozen when the subscription was created. customer — their own card. practice — the practice's saved card at wholesale (a practice-paid customer; see Customer.payer). Null on subscriptions from before the field existed, which are customer-paid.

object or null

What the next renewal is expected to cost. A stored snapshot, not a live quote — see the "Renewal amount" section of the Subscriptions guide, and use GET /v2/subscriptions/{subscriptionId}/renewal-estimate when you need a live figure. Null until an address is known.

subtotal
required
string

Product total after partner, customer and volume discounts

shipping
required
string

Shipping this renewal pays: "9.99" under $50 of product, "0.00" at $50 or more, or a bought upgrade's own price. Included in total.

tax
required
string

Tax at the saved shipping address; "0.00" when no address is on file

total
required
string

subtotal + shipping + tax

currency
required
string
daysSupply
required
integer

The cadence this estimate was priced for. Compare it with the subscription's daysSupply: if they differ, the cadence changed after the estimate was taken and the figure is out of date.

quotedAt
required
string <date-time>

When this estimate was produced

basis
required
string
Enum: "quote" "last_charge"

quote — priced now off the current regimen and catalogue. last_charge — the previous cycle's actual charge carried forward.

nextRetryAt
string or null

When the next retry is due, or null when no retry remains.

failureMessage
string or null

The processor's decline message, when there is one.

changedBy
string or null

Always dunning — the platform process that observed the failure.

initiatedBy
string or null
Enum: "PARTNER_API" "PARTNER_PORTAL" "CUSTOMER" "PLATFORM"

Always PLATFORM.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "subscription.payment_failed",
  • "created": 1753452000,
  • "data": {
    }
}

protocol.created Webhook

A protocol was created.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol.created" "protocol.updated" "protocol.archived" "protocol.reactivated"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol.updated Webhook

A protocol was updated through PUT /v2/protocols/{protocolId} or in the portal. Regimen changes applied through the amendment flow signal via protocol_change_request.approved instead.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol.created" "protocol.updated" "protocol.archived" "protocol.reactivated"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol.archived Webhook

A protocol was archived through DELETE /v2/protocols/{protocolId}, in the portal, or by the daily archiver after 60 days of inactivity.

Archiver-fired events carry a stub customer block — customer.id is populated and every other customer field is null — because the archiver runs outside a request and does not look the customer up.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol.created" "protocol.updated" "protocol.archived" "protocol.reactivated"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol.deactivated Webhook

A protocol was deactivated; its subscriptions, checkout sessions, amendments and scheduled changes were cancelled.

API-initiated deactivations add a sweep object counting what was cancelled. Portal-initiated ones send the protocol alone — treat sweep as optional.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "protocol.deactivated"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object (ProtocolDeactivatedWebhookPayload)

Event payload. Shape varies by type.

id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

object

What the deactivation cancelled. Present only when the deactivation came through POST /v2/protocols/{protocolId}/deactivate; portal-initiated deactivations send the protocol alone.

subscriptionsCancelled
integer
checkoutSessionsCancelled
integer
amendmentsCancelled
integer
scheduledChangesCancelled
integer

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol.deactivated",
  • "created": 1753452000,
  • "data": {
    }
}

protocol.reactivated Webhook

A deactivated protocol was reactivated.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol.created" "protocol.updated" "protocol.archived" "protocol.reactivated"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

id
required
string

Unique protocol identifier

required
object

Customer this protocol belongs to. Fields other than id are null when the customer record is unavailable.

id
required
string

Customer ID

sourceId
string or null

Customer's ID in the partner's system

firstName
string or null
lastName
string or null
email
string or null <email>
phone
string or null
required
Array of objects (ProtocolLineItem)

Products included in the protocol

Array
productId
required
integer
quantity
required
integer
name
string or null

Product name snapshot

object (Administration)

Dosage schedule information specifying how many pills to take at each time of day. Where dosing is validated (order and checkout line items), the sum of morning + afternoon + evening + sleep must equal the line item quantity. Omitted keys default to 0 in requests; responses always include all four keys.

packDescription
string or null
note
string or null
takeWith
string or null
Enum: "food" "water" "empty_stomach"
templateId
string or null

Protocol-template provenance identifier

templateSource
string or null
Enum: "partner" "juniper" null

Where the template this line came from originated

external
boolean

Whether the product is an external (non-pack) product

image
string or null

Product image URL (note the key is image on protocol views)

status
required
string
Enum: "ACTIVE" "INACTIVE" "ARCHIVED"

Current status of the protocol

orderType
required
string or null
Enum: "SUBSCRIPTION" "ONE_TIME"

Intended order type for the protocol. null until a one-time protocol fork sets it; today protocols are subscription-oriented and the order type is decided at checkout.

hasExternal
required
boolean

Whether the regimen includes any external (non-pack) products

createdAt
required
string <date-time>

Protocol creation timestamp

updatedAt
required
string <date-time>

Protocol last update timestamp

object or null

Server-produced pricing; use this instead of reconstructing money from unit prices or rates.

currency
string
Default: "USD"
status
string
Default: "estimate"
Enum: "estimate" "recorded"
source
string
Default: "legacy"
Enum: "exact" "frozen" "legacy"
basis
string
Default: "delivery"
Enum: "delivery" "30_pills" "item" "example"
daysSupply
integer or null
Default: null
shippingIncluded
boolean
Default: false
taxIncluded
boolean
Default: false
complete
boolean
Default: false
retailTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientDiscountAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
supplySavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
promoSavingsAmount
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
totalPatientSavings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
patientProductTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
shipping
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
tax
string^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: "0.00"
patientTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
billingMode
string or null
Default: null
Enum: "JUNIPER_MANAGED" "PARTNER_MANAGED"
discountSource
string
Default: "unknown"
Enum: "practice_default" "patient_override" "storefront" "recorded" "unknown"
productCost
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
fees
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
practiceTotal
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
earnings
string or null^(?!^[-+.]*$)[+-]?0*\d*\.?\d*$
Default: null
name
string or null <= 80 characters

Optional practitioner-facing label. Null for storefront-created protocols.

inactivatedAt
string or null <date-time>

When the protocol last went INACTIVE; null otherwise.

archivesAt
string or null <date-time>

When the daily job will archive an INACTIVE protocol: updatedAt + 60 days. Any edit moves it. Null unless INACTIVE.

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol_change_request.created Webhook

An amendment was sent for approval (draft to pending) — including sends that auto-approve, and consent amendments raised automatically by scheduled changes. Authoring a draft emits nothing; the lifecycle starts at send.

The protocol_change_request.* event NAMES predate the amendment model and are kept for wire continuity; the payload itself is amendment-native and camelCase like the rest of the API.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol_change_request.created" "protocol_change_request.approved" "protocol_change_request.rejected" "protocol_change_request.cancelled"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

changeRequestId
required
string

Alias for amendmentId — same value, kept for wire continuity.

amendmentId
required
string
protocolId
required
string
subscriptionId
required
string or null

Null for practitioner-authored protocol amendments with no subscription.

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Lowercase, unlike the REST resource.

required
Array of objects
Array
action
string
Enum: "add" "remove" "update"
targetItemId
string or null

Null for additions.

object or null
authoredBy
required
string
createdAt
required
string
approvedBy
string or null
approvedAt
string or null
rejectedBy
string or null
rejectedAt
string or null
cancellationReason
string or null

superseded when the platform cancelled a stale amendment

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol_change_request.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol_change_request.approved Webhook

An amendment was approved — by the customer on the review page, by you through the approve endpoint, or automatically during an attested or no-subscription send.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol_change_request.created" "protocol_change_request.approved" "protocol_change_request.rejected" "protocol_change_request.cancelled"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

changeRequestId
required
string

Alias for amendmentId — same value, kept for wire continuity.

amendmentId
required
string
protocolId
required
string
subscriptionId
required
string or null

Null for practitioner-authored protocol amendments with no subscription.

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Lowercase, unlike the REST resource.

required
Array of objects
Array
action
string
Enum: "add" "remove" "update"
targetItemId
string or null

Null for additions.

object or null
authoredBy
required
string
createdAt
required
string
approvedBy
string or null
approvedAt
string or null
rejectedBy
string or null
rejectedAt
string or null
cancellationReason
string or null

superseded when the platform cancelled a stale amendment

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol_change_request.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol_change_request.rejected Webhook

An amendment was rejected, by the customer or by you.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol_change_request.created" "protocol_change_request.approved" "protocol_change_request.rejected" "protocol_change_request.cancelled"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

changeRequestId
required
string

Alias for amendmentId — same value, kept for wire continuity.

amendmentId
required
string
protocolId
required
string
subscriptionId
required
string or null

Null for practitioner-authored protocol amendments with no subscription.

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Lowercase, unlike the REST resource.

required
Array of objects
Array
action
string
Enum: "add" "remove" "update"
targetItemId
string or null

Null for additions.

object or null
authoredBy
required
string
createdAt
required
string
approvedBy
string or null
approvedAt
string or null
rejectedBy
string or null
rejectedAt
string or null
cancellationReason
string or null

superseded when the platform cancelled a stale amendment

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol_change_request.created",
  • "created": 1753452000,
  • "data": {
    }
}

protocol_change_request.cancelled Webhook

You cancelled a draft or pending amendment. The 45-day automatic lapse of an unanswered amendment emits no event — poll the amendment if you need to observe it.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "protocol_change_request.created" "protocol_change_request.approved" "protocol_change_request.rejected" "protocol_change_request.cancelled"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

changeRequestId
required
string

Alias for amendmentId — same value, kept for wire continuity.

amendmentId
required
string
protocolId
required
string
subscriptionId
required
string or null

Null for practitioner-authored protocol amendments with no subscription.

status
required
string
Enum: "draft" "pending" "approved" "rejected" "cancelled" "expired"

Lowercase, unlike the REST resource.

required
Array of objects
Array
action
string
Enum: "add" "remove" "update"
targetItemId
string or null

Null for additions.

object or null
authoredBy
required
string
createdAt
required
string
approvedBy
string or null
approvedAt
string or null
rejectedBy
string or null
rejectedAt
string or null
cancellationReason
string or null

superseded when the platform cancelled a stale amendment

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "protocol_change_request.created",
  • "created": 1753452000,
  • "data": {
    }
}

refund.created Webhook

A refund was created against one of your orders; the refund is PROCESSING.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "refund.created" "refund.completed" "refund.failed"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

refundId
required
string
orderId
required
string
amount
required
integer

Integer cents.

refundType
required
string
Enum: "FULL" "PARTIAL"
status
required
string
Enum: "PENDING" "PROCESSING" "COMPLETED" "FAILED"
reason
required
string
failureReason
string or null

refund.failed only: why the refund failed, as the payment processor reported it (for example expired_or_canceled_card).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "refund.created",
  • "created": 1753452000,
  • "data": {
    }
}

refund.completed Webhook

The refund settled with the payment processor.

A card refund can still fail asynchronously, even days later, flipping the refund to FAILED; that sends refund.failed.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "refund.created" "refund.completed" "refund.failed"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

refundId
required
string
orderId
required
string
amount
required
integer

Integer cents.

refundType
required
string
Enum: "FULL" "PARTIAL"
status
required
string
Enum: "PENDING" "PROCESSING" "COMPLETED" "FAILED"
reason
required
string
failureReason
string or null

refund.failed only: why the refund failed, as the payment processor reported it (for example expired_or_canceled_card).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "refund.created",
  • "created": 1753452000,
  • "data": {
    }
}

refund.failed Webhook

The refund did not go through and is now FAILED: the payment processor rejected it when it was created, or reported it failed later (even days after refund.completed, in which case the order's refund accounting is reversed). failureReason carries the processor's reason when it gives one. Sent once per failure.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Enum: "refund.created" "refund.completed" "refund.failed"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

refundId
required
string
orderId
required
string
amount
required
integer

Integer cents.

refundType
required
string
Enum: "FULL" "PARTIAL"
status
required
string
Enum: "PENDING" "PROCESSING" "COMPLETED" "FAILED"
reason
required
string
failureReason
string or null

refund.failed only: why the refund failed, as the payment processor reported it (for example expired_or_canceled_card).

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "refund.created",
  • "created": 1753452000,
  • "data": {
    }
}

contact.received Webhook

A visitor submitted the contact form on your hosted storefront.

header Parameters
X-Juniper-Signature
required
string
Example: t=1753452005,v1=43cebf3abec6a3b49b4745b03fb5279786cdeab4b37377e5afa7990c0b1bb4ae

t=<unix seconds>,v1=<hex HMAC-SHA256>. The signature is HMAC-SHA256 over the string "{t}.{raw request body}", keyed with your endpoint secret as its 64 ASCII characters (do not hex-decode it). t is set per delivery attempt, not per event: a retry is re-signed with a fresh timestamp, so reject anything more than ~5 minutes old. Verify over the raw bytes before parsing the JSON.

X-Juniper-Webhook-Id
required
string
Example: whk_2zKQx4tR8vWn3kFh9YbT0cGdE5L

Unique id for this delivery attempt. A retry of the same event carries a new one, so deduplicate on the envelope's id instead. Not covered by the signature.

Request Body schema: application/json
required
id
required
string

Unique event id. Stable across retries and across endpoints — this is your deduplication key.

type
required
string
Value: "contact.received"

Event type.

created
required
integer <int64>

Unix timestamp (seconds) at which the event was generated.

required
object

Event payload. Shape varies by type.

id
required
string
partnerId
required
string
required
object

The submitted form fields, as sent.

property name*
additional property
any
page
required
string

Page path the form was submitted from.

receivedAt
required
string

Responses

Request samples

Content type
application/json
{
  • "id": "evt_2zKQx4tR8vWn3kFh9YbT0cGdE5L",
  • "type": "contact.received",
  • "created": 1753452000,
  • "data": {
    }
}