curl --request POST \
--url https://api.example.com/api/manual/generate-ai-caption \
--header 'Content-Type: application/json' \
--data '
{
"image": "<string>",
"context": {
"sessionTitle": "<string>",
"index": 123,
"total": 123,
"previousCaptions": [
"<string>"
],
"nextCaptions": [
"<string>"
],
"userSteering": "<string>",
"sessionStage": "<string>"
}
}
'{
"caption": "<string>",
"error": "<string>",
"requiresApiKey": true,
"code": "<string>"
}Generate contextual AI captions for manual session slides with narrative continuity
curl --request POST \
--url https://api.example.com/api/manual/generate-ai-caption \
--header 'Content-Type: application/json' \
--data '
{
"image": "<string>",
"context": {
"sessionTitle": "<string>",
"index": 123,
"total": 123,
"previousCaptions": [
"<string>"
],
"nextCaptions": [
"<string>"
],
"userSteering": "<string>",
"sessionStage": "<string>"
}
}
'{
"caption": "<string>",
"error": "<string>",
"requiresApiKey": true,
"code": "<string>"
}smart_captiondata:image/jpeg;base64,...)new_session - Writing fresh copy for new sessionexisting_session - Continuing established narrative{
"image": "https://example.com/slide-5.jpg",
"context": {
"sessionTitle": "Edging Challenge",
"index": 4,
"total": 20,
"previousCaptions": [
"Start stroking slowly...",
"Keep that pace, don't speed up yet.",
"Good boy. You're doing exactly what I want."
],
"nextCaptions": [
"Now stop. Hands off completely.",
"Count to 30 while you cool down."
],
"userSteering": "Keep it teasing and controlling, with countdown elements",
"sessionStage": "new_session"
}
}
{
"caption": "Speed up now. 10 strokes per second. I want to hear you struggling to keep up."
}
true if OPENROUTER_API_KEY is not configured.MODEL_IMAGE_UNSUPPORTED - Selected model doesn’t support images{
"error": "AI caption service is not available.",
"requiresApiKey": true
}
{
"error": "Animated GIFs are not supported for AI captions."
}
{
"error": "AI captions support static images only (JPEG, PNG, WebP)."
}
{
"error": "Image URL host is not allowed"
}
google/gemini-2.5-pro (configurable via OPENROUTER_MODEL_ID)google/gemini-2.5-flash-liteBLOCK_NONE thresholdsCONTENT_POLICY_REJECTION and EMPTY_RESPONSE_CONTENT_FILTEREDSession: {sessionTitle}
You are writing the next beat in an ongoing JOI narrative (index {index} of {total}).
Do not mention slide numbers or counts.
Session mode: {NEW SESSION WRITING | EXISTING SESSION CONTINUATION}
Context from surrounding slides (use only to keep narrative continuous):
Previous captions: [last 3]
Upcoming captions: [next 3]
Session creator guidance: {userSteering}
Instructions:
- Treat this as continuous narrative: continue from previous without restarting
- Write ONE explicit NSFW caption for CURRENT slide beat in JOI/instructional voice
- Maintain flow and escalate/sustain pace naturally
- If creator guidance provided, weave it into this caption when it fits
- Keep 50-400 characters in one paragraph (can be longer for narrative themes)
- Use image context only to avoid contradictions (0-1 subtle visual cue)
- Do not quote or recap earlier lines; write only the next beat
- Output ONLY the caption text
{
"temperature": 1.2,
"top_p": 0.9,
"frequency_penalty": 0.8,
"presence_penalty": 0.2
}
localhost, 127.0.0.1, ::1, 0.0.0.010.x.x.x, 192.168.x.x, 172.16-31.x.x169.254.x.xhttp: and https: only.gif, image/gif).mp4, .webm, video/*)requireCredits middlewaretry {
await deductCreditsOnSuccess(req, {
description: 'Manual AI caption generation',
relatedEntityType: 'manual_slide',
});
} catch (creditError) {
logger.error('[CREDIT_DEDUCTION_FAILED] feature=manual_caption');
// Error logged but user still gets caption
}
smart_caption/api/admin/pricing/api/generate-caption which is free and optimized for short captions.previousCaptions and nextCaptions significantly improves caption quality by maintaining narrative flow. Always include the last 3 and next 3 captions when available.userSteering parameter is treated as high-priority preference. Be specific about tone, structure, or themes you want incorporated.new_session when creating fresh sessions to set coherent narrative tone. Use existing_session when editing imported sessions to align with established voice.