Juice ReelJuice Reel

Juice Reel developer platform

Build with verified betting data

Connect a Juice Reel account with OAuth 2.0, then securely read subscriber relationships and the connected user's open or settled bets from your backend.

Authorization URLhttps://www.juicereel.com/oauth2/authorize
API base URLhttps://external-api.juicereel.com
1Create an applicationRegister callbacks and request only the scopes you need.
2Authorize with PKCEValidate state and exchange the short-lived code server-side.
3Call the APIUse a bearer token plus client ID for connected users, or your client ID and secret for your own account.

OAuth 2.0

Authorize a user

Send the user to Juice Reel to sign in and approve access. This is a browser redirect, not a server-to-server API call.

GEThttps://www.juicereel.com/oauth2/authorize
AuthenticationJuice Reel browser session
Required scopeNone
Keep the PKCE verifier on your backend. Only its S256 challenge is sent to Juice Reel.
See code example

Request query parameters

FieldTypeDescription
client_idrequiredstringYour approved OAuth client ID.
redirect_urirequiredstringAn exact redirect URI registered on the application.
response_typerequiredstringMust be code.
scoperequiredstringSpace-delimited requested scopes. Send an empty value for identity-only access.
staterequiredstringUnpredictable value created and validated by your application.
code_challengerequiredstringBase64url SHA-256 digest of your PKCE verifier.
code_challenge_methodrequiredstringMust be S256.

Response

302 · redirect
FieldTypeDescription
codestringShort-lived, single-use authorization code exchanged by your backend.
statestringThe original state. Reject the callback if it does not exactly match your stored value.

OAuth 2.0

Exchange an authorization code

Exchange the callback code for an access token and rotating refresh token. Perform this request only from your backend.

POST/oauth2/token
AuthenticationHTTP Basic: client ID and client secret
Required scopeNot applicable
SECURITY WARNING: Call this endpoint only from your backend. Never call it from browser or mobile frontend code. Your client secret must never be included in frontend application bundles, shipped to users, or exposed to clients.

Request query parameters

FieldTypeDescription
grant_typerequiredstringMust be authorization_code.
coderequiredstringCode received by your redirect URI.
redirect_urirequiredstringMust exactly match the URI used during authorization.
code_verifierrequiredstringOriginal PKCE verifier used to create the challenge.

Response

200 · application/json
FieldTypeDescription
access_tokenstringBearer token used for resource requests. Store server-side.
refresh_tokenstringSingle-use token used to obtain a replacement token pair.
token_typestringBearer.
expires_inintegerAccess-token lifetime in seconds; currently 3600.
scopestringSpace-delimited scopes granted by the user.

OAuth 2.0

Refresh tokens

Use a refresh token once to receive a new access token and a replacement refresh token.

POST/oauth2/token
AuthenticationHTTP Basic: client ID and client secret
Required scopeNot applicable
SECURITY WARNING: Call this endpoint only from your backend. Never call it from browser or mobile frontend code. Your client secret must never be included in frontend application bundles, shipped to users, or exposed to clients. Reusing a consumed refresh token revokes the token family and associated grant.

Request query parameters

FieldTypeDescription
grant_typerequiredstringMust be refresh_token.
refresh_tokenrequiredstringThe current unused refresh token.

Response

200 · application/json
FieldTypeDescription
access_tokenstringNew one-hour bearer token.
refresh_tokenstringReplacement refresh token. Discard the token used for this request.
token_typestringBearer.
expires_inintegerAccess-token lifetime in seconds.
scopestringScopes retained by the authorization grant.

OAuth 2.0

Revoke tokens

Revoke an access token or refresh-token grant when a user disconnects Juice Reel from your application.

POST/oauth2/revoke
AuthenticationHTTP Basic: client ID and client secret
Required scopeNot applicable
SECURITY WARNING: Call this endpoint only from your backend. Never call it from browser or mobile frontend code. Your client secret must never be included in frontend application bundles, shipped to users, or exposed to clients.

Request query parameters

FieldTypeDescription
tokenrequiredstringAccess token or refresh token to revoke.
token_type_hintstringOptional access_token or refresh_token hint.

Response

200 · application/json
FieldTypeDescription
HTTP 200empty objectRevocation is idempotent and does not reveal whether the token existed.

Checkout

Create a checkout session

Create a hosted checkout session that wraps the Juice Reel purchase flow. Redirect the user's browser to checkoutUrl; after checkout succeeds, Juice Reel shows the OAuth consent screen and then redirects to successUrl with a short-lived authorization code and the supplied state. A cancelled checkout redirects to cancelUrl with its status and state. Exchange the authorization code at /oauth2/token; access tokens and client secrets must never be placed in URLs.

POST/checkout/sessions
AuthenticationHTTP Basic: client ID and client secret
Required scopeNot applicable
SECURITY WARNING: Call this endpoint only from your backend. Never call it from browser or mobile frontend code. Your client secret must never be included in frontend application bundles, shipped to users, or exposed to clients.

Request body

FieldTypeDescription
successUrlrequiredstringExact redirect URI registered on the OAuth application for a successful checkout.
cancelUrlrequiredstringExact redirect URI registered on the OAuth application for a cancelled checkout.
scopesrequiredstring[]Requested OAuth scopes. May be an empty array for identity-only access; otherwise scopes must be unique and a subset of the scopes approved for the OAuth application.
staterequiredstringOpaque value generated by your backend and returned unchanged to your successUrl or cancelUrl.
codeChallengerequiredstringPKCE S256 code challenge generated by your backend for the authorization-code exchange.

Response

201 · application/json
FieldTypeDescription
checkoutSessionIdstringUnique identifier for the checkout session.
checkoutUrlstringURL to open in the user's browser to begin checkout.
expiresAtstring (ISO 8601)Time at which the checkout session expires.

Account

Get the connected user

Return the connected user ID represented by a bearer token, or the OAuth app owner when using client credentials.

GET/oauth2/me
Authentication
OAuth user accessBearer token + client ID
OR
Owner onlyClient ID + secret
Required scopeNone
Use bearer authentication to identify a connected user, or client credentials to identify the OAuth app owner. No optional scope is required.

Response

200 · application/json
FieldTypeDescription
idintegerStable Juice Reel user identifier for the authorization grant.
displayNamestringUser's current public display name.

Subscribers

List all subscribers

List everyone who has ever subscribed to the OAuth application owner, including expired and sandbox subscriptions.

GET/oauth2/subscribers/current
AuthenticationHTTP Basic: client ID and client secret
Required scopeNone
SECURITY WARNING: Call this endpoint only from your backend. Never call it from browser or mobile frontend code. Your client secret must never be included in frontend application bundles, shipped to users, or exposed to clients. The OAuth client credentials identify the application owner whose subscribers are returned.

Response

200 · application/json
FieldTypeDescription
subscribersarrayAll subscriber records for the OAuth application owner, including expired subscriptions, ordered by latest expiration and creation time.
subscriptionProductIdstringStable identifier of the subscription product purchased from the application owner.
userIdintegerSubscriber's stable Juice Reel user ID.
expiresDatedatetimeISO 8601 expiration or renewal boundary.
isCurrentlySubscribedbooleanTrue when expiresDate is later than the time of the API request.
User.idintegerSubscriber's stable Juice Reel user ID.
User.displayNamestringSubscriber's current Juice Reel display name.
SubscriptionProduct.durationstringBilling duration: WEEKLY, MONTHLY, or YEARLY.

Subscribers

Get subscription status

Check whether the user represented by the access token is currently subscribed to the OAuth application owner.

GET/oauth2/subscribers/status
AuthenticationBearer token and X-OAuth-Client-Id
Required scopesubscribers.read
Returns true without a subscription field when the authenticated user owns the OAuth application. Expired and never-subscribed users receive false without a subscription field.

Response

200 · application/json
FieldTypeDescription
isCurrentlySubscribedbooleanTrue when the authenticated user has a non-expired subscription to the OAuth application owner.
subscriptionobject | omittedActive subscription record. Omitted when no active record exists or the authenticated user owns the application.
subscription.subscriptionProductIdstringStable identifier of the purchased subscription product.
subscription.userIdintegerSubscriber's stable Juice Reel user ID.
subscription.expiresDatedatetimeISO 8601 expiration or renewal boundary.
subscription.isCurrentlySubscribedbooleanTrue when this subscription record has not expired.
subscription.SubscriptionProduct.durationstringBilling duration: WEEKLY, MONTHLY, or YEARLY.

Bets

List open bets

Return the connected user's open bets using cursor-based pagination.

GET/oauth2/bets/open
Authentication
OAuth user accessBearer token + client ID
OR
Owner onlyClient ID + secret
Required scopebets.open.read
If neither datePlacedAfter nor datePlacedBefore is supplied, all available open-bet history is considered. The after boundary is inclusive and the before boundary is exclusive. Results are ordered by descending bet ID. Never modify or decode the cursor to drive application logic.

Request query parameters

FieldTypeDescription
datePlacedAfterstringOptional inclusive lower bound for bet placement time, as an ISO 8601 datetime. Omit to include the earliest available bets.
datePlacedBeforestringOptional exclusive upper bound for bet placement time, as an ISO 8601 datetime. Omit to include the latest available bets.
cursorstringOpaque nextCursor from the previous response. Omit for the first page; reuse the same date filters when requesting the next page.

Response

200 · application/json
FieldTypeDescription
nextCursorstring | nullPass this opaque value as cursor on the next request. Null means paging is complete.
hasMorebooleanTrue when another page is available. Equivalent to nextCursor being non-null.
BetA bet in descending ID order for the connected Juice Reel user.

Bets

List settled bets

Return the connected user's closed and cancelled bets using cursor-based pagination.

GET/oauth2/bets/settled
Authentication
OAuth user accessBearer token + client ID
OR
Owner onlyClient ID + secret
Required scopebets.settled.read
If neither dateClosedAfter nor dateClosedBefore is supplied, all available settled-bet history is considered. The after boundary is inclusive and the before boundary is exclusive. A null nextCursor and hasMore: false indicate that paging is complete.

Request query parameters

FieldTypeDescription
dateClosedAfterstringOptional inclusive lower bound for settlement time, as an ISO 8601 datetime. Omit to include the earliest available settled bets.
dateClosedBeforestringOptional exclusive upper bound for settlement time, as an ISO 8601 datetime. Omit to include the latest available settled bets.
cursorstringOpaque nextCursor from the previous response. Omit for the first page; reuse the same date filters when requesting the next page.

Response

200 · application/json
FieldTypeDescription
nextCursorstring | nullPass this opaque value as cursor on the next request. Null means paging is complete.
hasMorebooleanTrue when another page is available. Equivalent to nextCursor being non-null.
BetA bet in descending ID order for the connected Juice Reel user.

Bets

List changed bets

Return the connected user's open and settled bets together in one cursor-paginated feed for reconciliation.

GET/oauth2/bets/changed
Authentication
OAuth user accessBearer token + client ID
OR
Owner onlyClient ID + secret
Required scopebets.open.read + bets.settled.read
Results include open, closed, and cancelled bets with no status filter. They are ordered by updatedAt descending, with bet ID descending as the tie-breaker. Use updatedAtAfter as an inclusive ISO 8601 lower bound when reconciling from a checkpoint. Store the newest updatedAt checkpoint from each page and stop when a returned bet is older than your stored checkpoint. Use nextCursor and hasMore for pagination; the cursor is opaque and must not be modified.

Request query parameters

FieldTypeDescription
updatedAtAfterstringOptional inclusive lower bound for updatedAt, as an ISO 8601 timestamp. Only bets updated at or after this time are returned.
cursorstringOpaque nextCursor from the previous response. Omit for the first page; reuse it until hasMore is false.

Response

200 · application/json
FieldTypeDescription
nextCursorstring | nullPass this opaque value as cursor on the next request. Null means paging is complete.
hasMorebooleanTrue when another page is available. Equivalent to nextCursor being non-null.
BetA bet in descending ID order for the connected Juice Reel user.

Usage

Rate limits

API requests are limited in five-minute buckets.

1,000 requestsPer IP address per five-minute bucket
25,000 requestsPer client ID per five-minute bucket

Need a higher limit? Email support@juiceintegration.com.

Errors

Common error responses

Resource errors use an error field. OAuth protocol errors use error and error_description.

400X-OAuth-Client-Id header is required

Every external API request must identify its OAuth client.

400cursor is invalid

The supplied bet cursor is malformed or was not issued by Juice Reel.

400invalid_request / invalid_grant

OAuth form fields are malformed, the code is expired or consumed, PKCE failed, or the refresh token is invalid.

401Invalid OAuth access token

The bearer token is missing, expired, revoked, belongs to another client, or its grant is no longer active.

401invalid_client

Client ID, client secret, or HTTP Basic authentication is invalid.

403The access token requires the requested scope

The grant does not contain the scope required by this resource.

403Only the OAuth client owner can access this resource

The subscribers endpoint may only be called with a token issued for the user who owns the OAuth application.

429Rate limit exceeded

The IP address or OAuth client ID has exceeded its five-minute request limit. Wait for the bucket to reset before retrying.

5xxInternal server error

A transient server failure occurred. Retry idempotent GET requests with exponential backoff.

Webhooks

How webhooks work

Webhooks let your server receive real-time events from Juice Reel without polling for updates.

Juice Reel sends an HTTP POST request to your configured destination endpoint with the data for the event bucket you selected. Verify the signature against the raw request body before parsing or processing the event, then return a successful 2xx response after accepting it.

Webhook flow

1. ConfigureAdd a destination endpoint and choose the event bucket it should receive.
2. ReviewJuice Reel reviews the endpoint before delivery begins.
3. ReceiveAccept signed POST requests and process each event on your server.
Verify the HMAC signature
Node.js
import express from "express";
import crypto from "node:crypto";

const app = express();

function verifyJuiceReelWebhook(rawBody, signature, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const received = Buffer.from(signature, "utf8");
  const computed = Buffer.from(expected, "utf8");

  return received.length === computed.length
    && crypto.timingSafeEqual(received, computed);
}

app.post(
  "/webhooks/juice-reel",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.get("X-Juice-Reel-Signature");
    const secret = process.env.JUICE_REEL_WEBHOOK_SIGNING_SECRET;

    if (!signature || !secret || !verifyJuiceReelWebhook(req.body, signature, secret)) {
      return res.sendStatus(401);
    }

    // Parse only after the HMAC has been verified.
    const event = JSON.parse(req.body.toString("utf8"));
    console.log("Juice Reel event:", event.type, event.data);
    return res.sendStatus(204);
  },
);

Webhooks

Subscription events

Receive real-time updates when a subscriber purchases, renews, or cancels a subscription.

Subscription event forwarding is in its early stages. It is recommended that you pair this with the REST API /oauth2/subscribers/current. This will help you keep an accurate log of users that are currently subscribed. We do not currently support retrying webhooks. This means that if your server goes down or a webhook is missed, your database may not accurately reflect your true subscriptions state. It is recommended to use this for realtime access updates but run a nightly job that reconciles your subscriber consensus table against the REST API.

Events

Event typeWhen it is sent
A user completes an initial subscription purchase.
An existing subscription successfully renews.
A subscription is cancelled or will not renew.
A subscription reaches its expiration date and access ends.

Payload fields

FieldTypeDescription
idstringUnique identifier for this webhook event.
typestringThe subscription event type selected in the event list above.
createdAtdatetimeISO 8601 timestamp for when Juice Reel created the event.
objectSubscription details associated with the event.

Request headers

The signature uses the signing secret shown once when you create the webhook endpoint.

Content-Typeapplication/json
X-Juice-Reel-Signaturesha256=<hex digest>
Create a checkout session | Juice Reel API Docs