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_idandclient_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
Authentication
All API requests require OAuth 2.0 authentication. The flow uses the Authorization Code grant type with the following steps:
- Redirect user to BentBox authorization page
- User logs in and authorizes your platform
- BentBox redirects back with authorization code
- Exchange authorization code for access token
- 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.
User Initiates Connection
User clicks "Connect BentBox" on your platform. Redirect them to the BentBox authorization URL.
Authorization Request
User is shown BentBox login page (if not logged in) and then an authorization consent screen.
User Approves
User authorizes your platform to access their BentBox account with the requested scopes.
Authorization Code
BentBox redirects back to your platform with an authorization code.
Token Exchange
Your server exchanges the authorization code for an access token and connection ID.
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 |
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
| Scenario | Recommended 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
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
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
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
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
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
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
--upload-file /path/to/video.mp4 \
-H "Content-Type: video/mp4" \
"https://bentbox-videos.s3.amazonaws.com/..."
Upload using Python
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 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
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:
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
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.
Browser loads your page
Your page includes the BentBox widget script and one or more data-bentbox-widget divs.
Widget calls your proxy
The JS widget calls data-proxy on your server — your API credentials never touch the browser.
Proxy calls BentBox API
Your bentbox-proxy.php authenticates with Basic Auth and forwards the /v1/api/get-box-data response.
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
| Attribute | Required | Description |
|---|---|---|
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
| Feature | Notes |
|---|---|
| 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 Status | Error Code | Description |
|---|---|---|
| — | 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
stateparameter in OAuth flows - Store
client_secretand 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: falsebefore 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.
