API Documentation
Complete guide to integrate Open Spoken AI into your applications
Authentication
Authenticate your API requests using your API key
Include your API key in the Authorization header of every request:
Authorization: Bearer YOUR_API_KEY
Endpoints
/api/v1/generateGenerate content using AI templates or freestyle
promptstringrequired- Your content requesttemplatestringoptional- Template code (see list below)optionsobjectoptional- Template-specific optionsmax_tokensnumberoptional- Max output tokens (default: 1000)/api/v1/balanceGet your current balance and usage stats
/api/v1/templatesList all available templates with their options
/api/v1/logsGet request history with pagination and filtering
Response Format
Standard API response structure
Success Response (200 OK):
{
"success": true,
"data": {
"content": "🧘♀️ Yoga instructor | Flow & flexibility...",
"template_used": "bio-instagram"
},
"usage": {
"input_tokens": 366,
"output_tokens": 50,
"total_tokens": 416,
"cost_usd": 0.0054
},
"balance": {
"remaining": 24.99
}
}Error Response:
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient balance to complete request"
}
}Error Codes
Possible error responses and their meanings
INVALID_API_KEYAPI key is missing, invalid, or revoked
INSUFFICIENT_BALANCENot enough balance to complete this request
RATE_LIMIT_EXCEEDEDToo many requests (max 10 per minute)
INVALID_TEMPLATETemplate code not found or invalid
VALIDATION_ERRORInvalid or missing required parameters
GENERATION_FAILEDAI generation failed, please retry
BILLING_FAILEDFailed to deduct balance, please contact support
Code Examples
Quick start examples in multiple languages
curl -X POST https://textly.openspoken.ai/api/v1/generate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Write a flirty bio for a yoga instructor",
"template": "bio-instagram",
"options": {
"tone": "flirty",
"include_emojis": true
},
"max_tokens": 200
}'Error Handling
Best practices for handling API errors
Always check the success field and handle errors gracefully:
Python:
import requests
import time
try:
response = requests.post(url, headers=headers, json=data)
result = response.json()
if result.get("success"):
print(result["data"]["content"])
else:
error = result.get("error", {})
if error.get("code") == "INSUFFICIENT_BALANCE":
print("⚠️ Low balance! Add funds at /api-dashboard/billing")
elif error.get("code") == "RATE_LIMIT_EXCEEDED":
print("⏳ Rate limit hit, waiting 60 seconds...")
time.sleep(60)
else:
print(f"Error: {error.get('message')}")
except requests.exceptions.RequestException as e:
print(f"Network error: {e}")Node.js:
async function generateContent(prompt) {
try {
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify({ prompt })
});
const data = await response.json();
if (!data.success) {
const errorCode = data.error?.code;
if (errorCode === 'INSUFFICIENT_BALANCE') {
throw new Error('Low balance. Add funds to continue.');
} else if (errorCode === 'RATE_LIMIT_EXCEEDED') {
// Retry after 60 seconds
await new Promise(resolve => setTimeout(resolve, 60000));
return generateContent(prompt); // Retry
} else {
throw new Error(data.error?.message || 'Unknown error');
}
}
return data.data.content;
} catch (error) {
console.error('API Error:', error.message);
throw error;
}
}💡 Best Practices:
- Always check
successfield before accessing data - Implement retry logic for
RATE_LIMIT_EXCEEDED - Monitor balance and show warnings at $5 threshold
- Log errors with request IDs for debugging
- Use exponential backoff for transient errors (500)
Request Logs & Filtering
Retrieve and filter request history with pagination
The /api/v1/logs endpoint supports pagination and filtering:
limitnumber- Number of records (max 100, default 50)offsetnumber- Records to skip (default 0)request_idstring- Filter by specific request ID (returns all matching logs)templatestring- Filter by template codestart_datestring- Filter from date (ISO 8601)end_datestring- Filter to date (ISO 8601)Example Request (with filters):
# Get all logs with request_id "campaign-001" curl -X GET "https://textly.openspoken.ai/api/v1/logs?request_id=campaign-001" \ -H "Authorization: Bearer YOUR_API_KEY" # Filter by template curl -X GET "https://textly.openspoken.ai/api/v1/logs?template=bio-instagram&limit=20" \ -H "Authorization: Bearer YOUR_API_KEY"
Response:
{
"success": true,
"logs": [
{
"request_id": "campaign-001",
"template": "bio-instagram",
"prompt": "Write a flirty bio for a yoga instructor",
"response": "🧘♀️ Yoga instructor | Flow & flexibility...",
"input_tokens": 366,
"output_tokens": 50,
"total_tokens": 416,
"cost_usd": 0.0054,
"generation_time_ms": 1250,
"webhook_url": "https://example.com/webhook",
"webhook_status": "success",
"created_at": "2026-01-07T10:30:00Z"
}
],
"pagination": {
"limit": 50,
"offset": 0,
"total": 1,
"has_more": false
}
}💡 Request ID Usage:
Use request_id as a batch identifier to group related generations. For example, use "campaign-black-friday" for all content in that campaign. The API will return ALL logs matching that request_id (not just one).
⚠️ Empty Results:
If no logs match your filters, you'll receive an empty array with total: 0 (not an error). This allows consistent response handling in your code.
Available Templates
Get full list via GET /api/v1/templates endpoint
Call /api/v1/templates to get all 40+ templates with their options.
Popular templates:
bio-twitterTwitter/X Biobio-instagramInstagram Bioppv-messagePPV Messagepost-captionSocial Media Captionsexting-scriptSexting Scriptpromo-textPromotional TextPricing & Balance
Token-based pricing with shared balance
💡 Shared Balance: Your balance is shared across all API keys. Add funds once, use everywhere.
Rate Limits
Default limits for all API keys