Getting Started

The BentBox API allows third-party platforms to integrate content upload functionality on behalf of BentBox creators. This enables your users to upload videos, photos, and other content directly from your platform to their BentBox accounts.

Prerequisites

  • Contact BentBox to register your platform and receive OAuth credentials
  • You will receive a client_id and client_secret
  • Register your OAuth redirect URIs
  • Choose your required scopes (e.g., upload_video, upload_photo)

📧 Get API Credentials

Contact api@bentbox.co to register your platform and receive your OAuth credentials. Include your platform name, redirect URIs, and required scopes.

Base URL

https://api.bentbox.co

Authentication

All API requests require OAuth 2.0 authentication. The flow uses the Authorization Code grant type with the following steps:

  1. Redirect user to BentBox authorization page
  2. User logs in and authorizes your platform
  3. BentBox redirects back with authorization code
  4. Exchange authorization code for access token
  5. Use access token to make API requests

OAuth 2.0 Flow

The complete OAuth flow for connecting a creator's BentBox account to your platform.

1

User Initiates Connection

User clicks "Connect BentBox" on your platform. Redirect them to the BentBox authorization URL.

2

Authorization Request

User is shown BentBox login page (if not logged in) and then an authorization consent screen.

3

User Approves

User authorizes your platform to access their BentBox account with the requested scopes.

4

Authorization Code

BentBox redirects back to your platform with an authorization code.

5

Token Exchange

Your server exchanges the authorization code for an access token and connection ID.

6

Store Credentials

Store the access_token, refresh_token, connection_id, and user_id securely in your database.

⚠️ Security Notice

Never expose your client_secret in client-side code. Token exchange must happen server-side only.

API Endpoints

The following endpoints are available for OAuth and content upload:

Method Endpoint Description
GET /oauth/authorize Initiate OAuth authorization
POST /oauth/token Exchange code for access token
POST /v1/content/video Create video metadata
POST /v1/content/upload-url Get presigned S3 upload URL
GET /v1/api/get-box-data Get box metadata, cover image and pricing
JS /widget/bentbox-widget.js Embeddable box card widget for partner sites

OAuth Authorization

GET /oauth/authorize

Redirect users to this endpoint to initiate the OAuth flow. Users will be prompted to log in (if not already) and authorize your platform.

Query Parameters

Parameter Type Required Description
client_id string Required Your platform's client ID
redirect_uri string Required Registered redirect URI
scope string Required Comma-separated scopes (e.g., upload_video,upload_photo)
state string Optional CSRF protection token (recommended)
response_type string Required Must be code

Example Request

// Redirect user to:
GET https://api.bentbox.co/oauth/authorize?
  client_id=mp_live_abc123...&
  redirect_uri=https://yourplatform.com/callback&
  scope=upload_video,upload_photo&
  state=random_csrf_token&
  response_type=code

Callback Response

After user approval, BentBox redirects to your redirect_uri with:

// Success callback:
https://yourplatform.com/callback?
  code=auth_abc123...&
  state=random_csrf_token

🔐 CSRF Protection

Always include a unique state parameter and verify it matches when the user returns. This prevents CSRF attacks.

✅ Automatic Login Handling

If the user is not signed in to BentBox when they arrive at the authorization URL, they will be redirected to the BentBox sign-in page automatically. Once they log in, BentBox returns them directly to the authorization screen to complete the flow — no additional handling is required on your side. The original OAuth parameters (client_id, redirect_uri, scope, state) are preserved throughout.

Affiliate & Referral Tracking

If your platform drives new creator sign-ups through the OAuth flow, you can ensure those accounts are attributed to your affiliate referral using the mechanisms described below.

📌 How It Works

The BentBox /signup page natively supports two query parameters for partner attribution: ref (your referral/affiliate code) and src (your partner identifier). When a new user signs up via a URL that includes these parameters, the account is attributed to your referral automatically.

Signup URL Parameters

Parameter Type Constraints Description
ref string Alphanumeric only, max 32 chars Your affiliate or referral code
src string Alphanumeric, hyphens & underscores, max 32 chars Your partner identifier (e.g. partnersite)

Recommended Approaches

Option 1 — Pre-land on the Signup Page (Recommended for new creator acquisition)

If you know the user is new to BentBox, redirect them to the signup page with your parameters before starting the OAuth flow. BentBox stores the src in a session cookie and passes the ref code through the signup form server-side. Once their account is created they can proceed through OAuth normally.

// Step 1 — send the user to signup with your attribution params
https://bentbox.co/signup?ref=YOUR_REF_CODE&src=partnersite

// Step 2 — after signup, redirect into the OAuth flow as normal
https://api.bentbox.co/oauth/authorize?
  client_id=mp_live_abc123...&
  redirect_uri=https://yourplatform.com/callback&
  scope=upload_video,upload_photo&
  state=random_csrf_token&
  response_type=code

Option 2 — Encode Attribution in the state Parameter

If you use a single OAuth entry point for both new and returning users, you can round-trip your attribution data through the state parameter. Encode a JSON object (base64) that includes your CSRF token alongside your ref and src values. BentBox returns state to your callback unchanged, where you can recover it.

Note: this approach is most useful for attributing existing BentBox users connecting via OAuth. For brand-new accounts created mid-flow, use Option 1 to ensure ref and src are captured at the point of account creation.

// Encode attribution data alongside your CSRF token
const statePayload = btoa(JSON.stringify({
  csrf:  "random_csrf_token",
  ref:   "YOUR_REF_CODE",
  src:   "partnersite"
}));

// Pass as the state parameter in your authorize redirect
https://api.bentbox.co/oauth/authorize?
  client_id=mp_live_abc123...&
  redirect_uri=https://yourplatform.com/callback&
  scope=upload_video,upload_photo&
  state={statePayload}&
  response_type=code

// On your callback, decode state to recover ref/src
const { csrf, ref, src } = JSON.parse(atob(callbackState));

Choosing the Right Approach

ScenarioRecommended Approach
User is new to BentBox Option 1 — pre-land on /signup?ref=…&src=… before OAuth
User already has a BentBox account Option 2 — encode ref/src in state for your own attribution records
Unknown (mixed audience) Option 1 — new users sign up with attribution, existing users simply log in and the OAuth flow continues normally

⚠️ Register Your Referral Code First

Your ref code must be registered with BentBox before attribution can be tracked. Contact api@bentbox.co to confirm your code is active.

Token Exchange

POST /oauth/token

Exchange the authorization code for an access token. This request must be made from your server.

Request Body (JSON)

Parameter Type Required Description
grant_type string Required Must be authorization_code
code string Required Authorization code from callback
client_id string Required Your platform's client ID
client_secret string Required Your platform's client secret
redirect_uri string Required Must match the redirect_uri from authorization

Example Request

POST https://api.bentbox.co/oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "code": "auth_abc123...",
  "client_id": "mp_live_abc123...",
  "client_secret": "mp_secret_xyz...",
  "redirect_uri": "https://yourplatform.com/callback"
}

Response (201 Created)

{
  "access_token": "bx_live_abc123...",
  "refresh_token": "bx_refresh_xyz...",
  "token_type": "Bearer",
  "expires_in": 86400,
  "connection_id": "conn_abc123...",
  "user_id": "55-1420773031-54af...",
  "scopes": ["upload_video", "upload_photo"]
}

✅ Store These Values

Save the access_token, refresh_token, connection_id, and user_id in your database. You'll need them for all future API calls.

Create Video Metadata

POST /v1/content/video

Create video metadata and reserve a video ID before uploading the actual file. This is the first step in the upload process.

Headers

Header Value
Authorization Bearer {access_token}
Content-Type application/json

Request Body (JSON)

Parameter Type Required Description
connection_id string Required Connection ID from token exchange
title string Required Video title (max 200 chars)
filename string Required File name with extension (.mp4, .mov, etc.)
filesize integer Required File size in bytes (max 2GB)
duration number Required Video duration in seconds. Required — used for duration limit enforcement at the upload-url step.
description string Optional Video description (max 5000 chars)
tags array Optional Array of tags (max 20, each max 50 chars)
price number Optional Price in USD (0.99-999.99)
is_premium boolean Optional Premium content flag

Example Request

POST https://api.bentbox.co/v1/content/video
Authorization: Bearer bx_live_abc123...
Content-Type: application/json

{
  "connection_id": "conn_abc123...",
  "title": "My Amazing Video",
  "filename": "video.mp4",
  "filesize": 524288000,
  "duration": 300,
  "description": "This is a test video",
  "tags": ["outdoor", "summer"],
  "price": 9.99,
  "is_premium": true
}

Response (201 Created)

{
  "success": true,
  "video_id": "api-1770903940-db122f8a...",
  "status": "awaiting_upload",
  "upload_expires_at": "2026-02-16T22:00:00Z",
  "message": "Video metadata created. Use video_id to get upload URL."
}

Get Upload URL

POST /v1/content/upload-url

Get a presigned S3 URL to upload the video file directly to BentBox's storage. This URL is valid for 24 hours.

Headers

Header Value
Authorization Bearer {access_token}
Content-Type application/json

Request Body (JSON)

Parameter Type Required Description
connection_id string Required Connection ID from token exchange
video_id string Required Video ID from metadata creation
title string Required Video title (max 255 chars)
price number Required Sale price in USD (0 for free, max 9999.99)
duration number Required Video duration in seconds. Used to enforce per-account length limits. Default maximum is 15 minutes.
description string Optional Video description (max 5000 chars)
tags array Optional Array of tag strings (max 20 items)
adult_content boolean Optional Whether the video contains adult content. Defaults to true.

⚠️ Account Verification Required

The creator's BentBox account must be fully verified before upload URLs can be generated. If verification is incomplete, the API returns a verification_required error. Direct the user to complete verification in their BentBox account settings.

Example Request

POST https://api.bentbox.co/v1/content/upload-url
Authorization: Bearer bx_live_abc123...
Content-Type: application/json

{
  "connection_id": "conn_abc123...",
  "video_id": "api-1770903940-db122f8a...",
  "title": "My Amazing Video",
  "price": 9.99,
  "duration": 342.5,
  "description": "Optional description",
  "tags": ["outdoor", "summer"],
  "adult_content": true
}

Response (200 OK)

{
  "success": true,
  "upload_url": "https://bentbox-videos.s3.amazonaws.com/...",
  "upload_method": "PUT",
  "expires_in": 86400,
  "expires_at": "2026-02-16T22:00:00Z",
  "filesize": 524288000,
  "content_type": "video/*"
}

🔄 Re-requesting URLs

You can request a new upload URL for the same video_id if the previous one expired or if the upload failed. The URL expires after 24 hours or when the upload window closes.

Upload Video File

Use the presigned URL from the previous step to upload the video file directly to S3 using an HTTP PUT request.

Upload using cURL

curl -X PUT \
  --upload-file /path/to/video.mp4 \
  -H "Content-Type: video/mp4" \
  "https://bentbox-videos.s3.amazonaws.com/..."

Upload using Python

import requests

with open('/path/to/video.mp4', 'rb') as f:
    response = requests.put(
        upload_url,
        data=f,
        headers={'Content-Type': 'video/mp4'}
    )
    print(response.status_code) # Should be 200

Upload using Node.js

const fs = require('fs');
const fetch = require('node-fetch');

const fileBuffer = fs.readFileSync('/path/to/video.mp4');

fetch(uploadUrl, {
  method: 'PUT',
  body: fileBuffer,
  headers: { 'Content-Type': 'video/mp4' }
}).then(res => console.log(res.status));

✅ Automatic Processing

Once the file is uploaded to S3, BentBox's processing pipeline automatically detects the upload, transcodes the video, generates thumbnails, and makes it available on the creator's profile. No additional API call is required.

Complete Integration Examples

Full, production-ready code examples showing the complete OAuth and upload flow in popular languages.

Node.js / Express

// Install dependencies: npm install express axios
            const express = require('express');
            const axios = require('axios');
            const fs = require('fs');
            const crypto = require('crypto');

            const app = express();
            const sessions = new Map(); // Use Redis in production

            const BENTBOX_CONFIG = {
              clientId: 'mp_live_abc123...',
              clientSecret: 'mp_secret_xyz...',
              redirectUri: 'https://yourplatform.com/oauth/callback',
              authUrl: 'https://api.bentbox.co/oauth/authorize',
              tokenUrl: 'https://api.bentbox.co/oauth/token',
              apiBase: 'https://api.bentbox.co'
            };

            // Step 1: Initiate OAuth flow
            app.get('/connect-bentbox', (req, res) => {
              const state = crypto.randomBytes(16).toString('hex');
              sessions.set(state, { userId: req.user.id });

              const authUrl = `${BENTBOX_CONFIG.authUrl}?` +
                `client_id=${BENTBOX_CONFIG.clientId}&` +
                `redirect_uri=${encodeURIComponent(BENTBOX_CONFIG.redirectUri)}&` +
                `scope=upload_video,upload_photo&` +
                `state=${state}&` +
                `response_type=code`;

              res.redirect(authUrl);
            });

            // Step 2: Handle OAuth callback
            app.get('/oauth/callback', async (req, res) => {
              const { code, state } = req.query;

              if (!sessions.has(state)) {
                return res.status(400).send('Invalid state');
              }

              try {
                const response = await axios.post(BENTBOX_CONFIG.tokenUrl, {
                  grant_type: 'authorization_code',
                  code,
                  client_id: BENTBOX_CONFIG.clientId,
                  client_secret: BENTBOX_CONFIG.clientSecret,
                  redirect_uri: BENTBOX_CONFIG.redirectUri
                });

                const { access_token, refresh_token, connection_id, user_id } = response.data;

                // Store tokens in database (encrypted!)
                await db.saveConnection({
                  userId: sessions.get(state).userId,
                  bentboxUserId: user_id,
                  connectionId: connection_id,
                  accessToken: encrypt(access_token),
                  refreshToken: encrypt(refresh_token)
                });

                sessions.delete(state);
                res.redirect('/dashboard?connected=true');
              } catch (error) {
                console.error('Token exchange failed:', error.response?.data);
                res.status(500).send('Connection failed');
              }
            });

            // Step 3: Upload video
            app.post('/upload-video', async (req, res) => {
              const { title, description, videoPath } = req.body;
              const connection = await db.getConnection(req.user.id);
              const accessToken = decrypt(connection.accessToken);

              try {
                // Create video metadata
                const fileStats = fs.statSync(videoPath);
                const createRes = await axios.post(
                  `${BENTBOX_CONFIG.apiBase}/v1/content/video`,
                  {
                    connection_id: connection.connectionId,
                    title,
                    description,
                    filename: 'video.mp4',
                    filesize: fileStats.size,
                    tags: ['uploaded', 'from-platform']
                  },
                  { headers: { 'Authorization': `Bearer ${accessToken}` } }
                );

                const { video_id } = createRes.data;

                // Get upload URL (includes title, price, duration for validation)
                const urlRes = await axios.post(
                  `${BENTBOX_CONFIG.apiBase}/v1/content/upload-url`,
                  {
                    connection_id: connection.connectionId,
                    video_id,
                    title,
                    price: req.body.price ?? 0,
                    duration: req.body.duration,  // seconds, read from file metadata
                    description: req.body.description ?? '',
                    tags: req.body.tags ?? [],
                    adult_content: true
                  },
                  { headers: { 'Authorization': `Bearer ${accessToken}` } }
                );

                const { upload_url } = urlRes.data;

                // Upload file to S3
                const fileBuffer = fs.readFileSync(videoPath);
                await axios.put(upload_url, fileBuffer, {
                  headers: { 'Content-Type': 'video/mp4' },
                  maxContentLength: 2147483648
                });

                res.json({ success: true, video_id });
              } catch (error) {
                console.error('Upload failed:', error.response?.data);
                res.status(500).json({ 
                  success: false, 
                  error: error.response?.data?.error || 'Upload failed'
                });
              }
            });

            app.listen(3000);

Python / Flask

# Install: pip install flask requests
            from flask import Flask, redirect, request, session
            import requests
            import secrets
            import os

            app = Flask(__name__)
            app.secret_key = os.urandom(24)

            BENTBOX_CONFIG = {
                'client_id': 'mp_live_abc123...',
                'client_secret': 'mp_secret_xyz...',
                'redirect_uri': 'https://yourplatform.com/oauth/callback',
                'auth_url': 'https://api.bentbox.co/oauth/authorize',
                'token_url': 'https://api.bentbox.co/oauth/token',
                'api_base': 'https://api.bentbox.co'
            }

            # Step 1: Initiate OAuth
            @app.route('/connect-bentbox')
            def connect_bentbox():
                state = secrets.token_urlsafe(16)
                session['oauth_state'] = state
                
                auth_url = f"{BENTBOX_CONFIG['auth_url']}?" \
                    f"client_id={BENTBOX_CONFIG['client_id']}&" \
                    f"redirect_uri={BENTBOX_CONFIG['redirect_uri']}&" \
                    f"scope=upload_video,upload_photo&" \
                    f"state={state}&" \
                    f"response_type=code"
                
                return redirect(auth_url)

            # Step 2: Handle callback
            @app.route('/oauth/callback')
            def oauth_callback():
                code = request.args.get('code')
                state = request.args.get('state')
                
                if state != session.get('oauth_state'):
                    return 'Invalid state', 400
                
                try:
                    response = requests.post(BENTBOX_CONFIG['token_url'], json={
                        'grant_type': 'authorization_code',
                        'code': code,
                        'client_id': BENTBOX_CONFIG['client_id'],
                        'client_secret': BENTBOX_CONFIG['client_secret'],
                        'redirect_uri': BENTBOX_CONFIG['redirect_uri']
                    })
                    
                    data = response.json()
                    
                    # Store in database
                    db.save_connection(
                        user_id=session['user_id'],
                        bentbox_user_id=data['user_id'],
                        connection_id=data['connection_id'],
                        access_token=encrypt(data['access_token']),
                        refresh_token=encrypt(data['refresh_token'])
                    )
                    
                    return redirect('/dashboard?connected=true')
                except Exception as e:
                    return f'Connection failed: {str(e)}', 500

            # Step 3: Upload video
            @app.route('/upload-video', methods=['POST'])
            def upload_video():
                title = request.json['title']
                video_path = request.json['video_path']
                
                connection = db.get_connection(session['user_id'])
                access_token = decrypt(connection.access_token)
                
                try:
                    # Create metadata
                    file_size = os.path.getsize(video_path)
                    metadata_response = requests.post(
                        f"{BENTBOX_CONFIG['api_base']}/v1/content/video",
                        json={
                            'connection_id': connection.connection_id,
                            'title': title,
                            'filename': 'video.mp4',
                            'filesize': file_size
                        },
                        headers={'Authorization': f'Bearer {access_token}'}
                    )
                    video_id = metadata_response.json()['video_id']
                    
                    # Get upload URL
                    url_response = requests.post(
                        f"{BENTBOX_CONFIG['api_base']}/v1/content/upload-url",
                        json={'connection_id': connection.connection_id, 'video_id': video_id},
                        headers={'Authorization': f'Bearer {access_token}'}
                    )
                    upload_url = url_response.json()['upload_url']
                    
                    # Upload file
                    with open(video_path, 'rb') as f:
                        requests.put(upload_url, data=f, headers={'Content-Type': 'video/mp4'})
                    
                    return {'success': True, 'video_id': video_id}
                except Exception as e:
                    return {'success': False, 'error': str(e)}, 500

PHP

<?php
            // Configuration
            define('CLIENT_ID', 'mp_live_abc123...');
            define('CLIENT_SECRET', 'mp_secret_xyz...');
            define('REDIRECT_URI', 'https://yourplatform.com/callback.php');
            define('API_BASE', 'https://api.bentbox.co');

            // Step 1: Initiate OAuth
            function initiateBentBoxOAuth() {
                $state = bin2hex(random_bytes(16));
                $_SESSION['oauth_state'] = $state;
                
                $params = [
                    'client_id' => CLIENT_ID,
                    'redirect_uri' => REDIRECT_URI,
                    'scope' => 'upload_video,upload_photo',
                    'state' => $state,
                    'response_type' => 'code'
                ];
                
                $authUrl = API_BASE . '/oauth/authorize?' . http_build_query($params);
                header("Location: $authUrl");
                exit;
            }

            // Step 2: Handle callback
            function handleOAuthCallback() {
                $code = $_GET['code'] ?? null;
                $state = $_GET['state'] ?? null;
                
                if ($state !== $_SESSION['oauth_state']) {
                    die('Invalid state');
                }
                
                $ch = curl_init(API_BASE . '/oauth/token');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
                    'grant_type' => 'authorization_code',
                    'code' => $code,
                    'client_id' => CLIENT_ID,
                    'client_secret' => CLIENT_SECRET,
                    'redirect_uri' => REDIRECT_URI
                ]));
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
                
                $response = curl_exec($ch);
                $data = json_decode($response, true);
                curl_close($ch);
                
                // Store in database (encrypted!)
                saveConnection([
                    'user_id' => $_SESSION['user_id'],
                    'connection_id' => $data['connection_id'],
                    'access_token' => encrypt($data['access_token']),
                    'refresh_token' => encrypt($data['refresh_token'])
                ]);
                
                header('Location: /dashboard?connected=true');
            }

            // Step 3: Upload video
            function uploadVideo($title, $videoPath) {
                $connection = getConnection($_SESSION['user_id']);
                $accessToken = decrypt($connection['access_token']);
                
                // Create metadata
                $ch = curl_init(API_BASE . '/v1/content/video');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
                    'connection_id' => $connection['connection_id'],
                    'title' => $title,
                    'filename' => 'video.mp4',
                    'filesize' => filesize($videoPath)
                ]));
                curl_setopt($ch, CURLOPT_HTTPHEADER, [
                    'Authorization: Bearer ' . $accessToken,
                    'Content-Type: application/json'
                ]);
                
                $response = json_decode(curl_exec($ch), true);
                $videoId = $response['video_id'];
                curl_close($ch);
                
                // Get upload URL
                $ch = curl_init(API_BASE . '/v1/content/upload-url');
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_POST, true);
                curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
                    'connection_id' => $connection['connection_id'],
                    'video_id'      => $videoId,
                    'title'         => $title,
                    'price'         => $price,
                    'duration'      => $duration,  // seconds, from file metadata
                    'description'   => $description,
                    'tags'          => $tags,
                    'adult_content' => true
                ]));
                curl_setopt($ch, CURLOPT_HTTPHEADER, [
                    'Authorization: Bearer ' . $accessToken,
                    'Content-Type: application/json'
                ]);
                
                $response = json_decode(curl_exec($ch), true);
                $uploadUrl = $response['upload_url'];
                curl_close($ch);
                
                // Upload to S3
                $ch = curl_init($uploadUrl);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($ch, CURLOPT_PUT, true);
                curl_setopt($ch, CURLOPT_INFILE, fopen($videoPath, 'rb'));
                curl_setopt($ch, CURLOPT_INFILESIZE, filesize($videoPath));
                curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: video/mp4']);
                
                curl_exec($ch);
                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                curl_close($ch);
                
                return $httpCode === 200;
            }

💡 Pro Tips

  • Always encrypt tokens before storing in your database
  • Use environment variables for client credentials
  • Implement proper error handling for network failures
  • Add upload progress tracking for better UX
  • Handle token refresh when access tokens expire

Get Box Data

GET /v1/api/get-box-data

Returns metadata, a signed cover image URL, and pricing for a given box. Authentication uses your API key and secret via HTTP Basic Auth — no OAuth flow required.

Authentication

Pass your API key and secret as HTTP Basic Auth credentials:

Authorization: Basic base64(api_key:api_secret)

Query Parameters

Parameter Type Required Description
boxId string Required The BentBox box ID to retrieve
promo_code string Optional Promo code to apply to pricing

Example Request

GET https://api.bentbox.co/v1/api/get-box-data?boxId=YgyvDwSC
Authorization: Basic <base64(api_key:api_secret)>
Accept: application/json

Example Response (200 OK)

{
  "selling_price": "13.99",
  "selling_price_with_transaction_fees": "15.38",
  "user_price": "8.99",
  "transaction_fees": "1.39",
  "referral_fee": "0.00",
  "discount_value": "0.00",
  "discount_percent": "0.00",
  "promo_code_valid": "false",
  "cover_image_url": "https://d6ijsqg8e9unz.cloudfront.net/...",
  "cover_image_expires_in": 600,
  "box_name": "My Box Title",
  "box_description": "Box description here",
  "box_tags": "tag1,tag2,tag3"
}

Response Fields

Field Type Description
selling_price string Box price before transaction fees (2 decimal places)
selling_price_with_transaction_fees string Total price the buyer pays including fees (2 decimal places)
user_price string Amount the creator receives (2 decimal places)
transaction_fees string Transaction fee amount (2 decimal places)
referral_fee string Referral fee if applicable (2 decimal places)
discount_value string Discount amount applied (2 decimal places)
discount_percent string Discount percentage applied (2 decimal places)
promo_code_valid string "true" or "false" — whether the promo code was applied
cover_image_url string Signed CloudFront URL for the box cover image. Valid for cover_image_expires_in seconds — do not cache.
cover_image_expires_in integer Seconds until the signed cover URL expires (600 = 10 minutes)
box_name string Box title
box_description string Box description
box_tags string Comma-separated tags for the box

⏱ Cover Image Expiry

The cover_image_url is a signed CloudFront URL valid for 10 minutes. Request it fresh each time you need to display the cover image — do not store or cache the URL.

PHP Example

$apiKey    = 'mp_live_...';
$apiSecret = 'mp_secret_...';
$boxId     = 'YgyvDwSC';

$url         = 'https://api.bentbox.co/v1/api/get-box-data?boxId=' . urlencode($boxId);
$credentials = base64_encode($apiKey . ':' . $apiSecret);

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Basic ' . $credentials,
        'Accept: application/json',
    ],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Box Embed Widget

Embed a BentBox box card directly on your site with a single <script> tag and a <div>. The widget fetches live box data through a lightweight proxy on your server (keeping your API key safe server-side) and renders a fully styled card with cover image, pricing, discount badge, tags, and a buy button. Buy Now links automatically include UTM parameters so referral traffic from partner sites is correctly attributed in Google Analytics.

1

Browser loads your page

Your page includes the BentBox widget script and one or more data-bentbox-widget divs.

2

Widget calls your proxy

The JS widget calls data-proxy on your server — your API credentials never touch the browser.

3

Proxy calls BentBox API

Your bentbox-proxy.php authenticates with Basic Auth and forwards the /v1/api/get-box-data response.

4

Widget renders the card

Box name, description, tags, cover image and price are displayed. Clicking Buy Now sends the user to BentBox checkout.

Step 1 — Add the proxy to your server

Create bentbox-proxy.php on your server. This is the only file that holds your API credentials — it is never sent to the browser.

// bentbox-proxy.php — place on YOUR server
$apiKey    = 'mp_live_...';   // your API key
$apiSecret = 'mp_secret_...'; // your API secret

header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');

$boxId = preg_replace('/[^a-zA-Z0-9]/', '', $_GET['boxId'] ?? '');
if (!$boxId) { http_response_code(400); exit; }

$ch = curl_init('https://api.bentbox.co/v1/api/get-box-data?boxId=' . urlencode($boxId));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Basic ' . base64_encode("$apiKey:$apiSecret"),
        'Accept: application/json',
    ],
]);
$response   = curl_exec($ch);
$httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

http_response_code($httpStatus);
echo $response;

Step 2 — Embed the widget

Add a <div> with the required data attributes wherever you want a box card to appear, then load the widget script once at the bottom of <body>. Multiple boxes on the same page are all initialised automatically.

<!-- One div per box -->
<div data-bentbox-widget
     data-box-id="YgyvDwSC"
     data-proxy="/bentbox-proxy.php"
     data-buy-url="https://bentbox.co/buybox?YgyvDwSC"
     data-utm-source="your-site-name">
</div>

<!-- Load widget script once, before </body> -->
<script src="https://api.bentbox.co/widget/bentbox-widget.js"></script>

Widget Attributes

AttributeRequiredDescription
data-box-id Required The BentBox box ID to display
data-proxy Required URL of your bentbox-proxy.php on your server. Keeps credentials server-side.
data-buy-url Optional Override the Buy Now link. Defaults to https://bentbox.co/buybox?{boxId}
data-utm-source Optional UTM source value appended to the buy URL for GA referral tracking. Defaults to bentbox-widget. Set this to your site name (e.g. bentmodels) for per-partner attribution in Google Analytics.

Widget Features

FeatureNotes
Cover image Loaded from signed CloudFront URL returned by the API. Hidden gracefully if unavailable.
Discount badge Automatically shown when discount_percent > 0. Displays original and discounted price.
Free pricing Displays Free in green when selling_price is 0.00.
Tags Up to 5 tags shown from box_tags.
Loading / error states Animated spinner while fetching; graceful error message if unavailable.
UTM tracking Automatically appends utm_source, utm_medium=referral, and utm_campaign=partner-embed to the buy URL. Override the source via data-utm-source for per-partner GA attribution.
No dependencies Self-contained — no jQuery, no frameworks. Styles injected automatically on first load.

Dynamic Initialisation

If you insert widget <div> elements after the page has loaded (e.g. via AJAX), call:

window.BentBoxWidget.init();

This re-scans the DOM for any uninitialised data-bentbox-widget elements.

🔑 Keep Your API Key Server-Side

Never put your API key or secret in client-side JavaScript or HTML. The proxy pattern above ensures your credentials are only ever used in a server-to-server call. The widget JS hosted on api.bentbox.co contains no credentials.

📦 Files Summary

On api.bentbox.co (hosted by BentBox): https://api.bentbox.co/widget/bentbox-widget.js (v1.2)
On your server: bentbox-proxy.php — add your API key and secret here, never expose this file publicly as source.

Error Codes

The API uses standard HTTP status codes and returns errors as JSON.

Error Response Format

{
  "success": false,
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
HTTP StatusError CodeDescription
access_denied User cancelled on the authorization screen. Returned as a query parameter to your redirect_uri, not an HTTP error.
400 invalid_request Missing or invalid parameters
400 invalid_grant Authorization code expired or already used
400 invalid_client Invalid client credentials
400 invalid_scope Requested scope is not permitted for this client
401 unauthorized Missing or invalid API key / access token
403 forbidden Access token expired or insufficient permissions for this operation
403 verification_required Creator account not fully verified. User must complete identity verification in BentBox account settings before uploading.
403 duration_exceeded Video duration exceeds the maximum allowed length. Response includes max_duration_seconds and declared_duration_seconds.
404 not_found Resource not found (box, connection, video, etc.)
429 rate_limit_exceeded Too many requests. Slow down and retry with backoff.

Duration Error Example

{
  "success": false,
  "error": "duration_exceeded",
  "error_description": "Video duration exceeds the maximum allowed length of 15 minutes.",
  "max_duration_seconds": 900,
  "declared_duration_seconds": 1240.5
}

Best Practices

Security

🔐 Token Storage

Store access tokens and refresh tokens in encrypted database fields. Never expose them in URLs, logs, or client-side code.

  • Always use HTTPS for all API requests
  • Implement CSRF protection using the state parameter in OAuth flows
  • Store client_secret and API keys in environment variables or a secrets manager — never in source code
  • Use the proxy pattern for widget embeds to keep API credentials server-side
  • Rotate access tokens on expiry using refresh tokens

Error Handling

  • Always check for success: false before using response data
  • Handle 403 forbidden (expired token) by refreshing and retrying once
  • Retry failed uploads with exponential backoff
  • Log full error responses server-side but show only safe messages to end users

Upload Optimisation

  • Validate file size and MIME type before requesting an upload URL
  • Request a new upload URL if more than 10 minutes have passed since it was issued
  • Show upload progress to users — the S3 PUT returns 200 on success
  • Handle network interruptions with retry logic on the S3 PUT step

User Experience

  • Clearly explain what permissions you are requesting and why before redirecting to the authorization screen
  • Allow users to disconnect their BentBox account from your platform at any time
  • Notify users when their uploaded content is live on BentBox
  • Do not cache signed cover image URLs — they expire after 10 minutes

📊 Rate Limits

Current rate limits: 100 requests per hour per API key. Contact api@bentbox.co for higher limits if your use case requires it.