CyberStock uses analytics to improve our service. You can learn more in ourPrivacy Policy.
    API v1.0

    CyberStock API

    Programmatic access to the CyberStock AI Engine for stock photo and video metadata. Titles, descriptions, keywords, and Selling Score in one API call, powered by real buyer searches.

    Base URL
    Copy your exact base URL from Account → API (inside the app). Use it as CYBERSTOCK_API_BASE.
    bash
    export CYBERSTOCK_API_BASE="https://<your-api-base>/functions/v1/api-gateway"

    Overview

    The CyberStock API lets you process images and videos and get stock-ready metadata: titles, descriptions, keywords, categories, and Selling Score. It uses the same AI Engine as the CyberStock web app.

    Photo & Video
    Process JPG, PNG, WebP, TIFF, MP4, MOV, and more
    Metadata Generation
    Generate titles, descriptions, and up to 50 keywords
    Scoring
    Get selling scores to evaluate commercial potential

    Authentication

    All API requests require an API key sent via the X-API-Key header.

    Getting Your API Key
    1. Sign in to CyberStock and go to your Account panel
    2. Click the API tab
    3. Click Generate Key. Your key starts with cs_live_
    4. Copy and save the key securely. It's shown only once
    Header
    X-API-Key: cs_live_YOUR_API_KEY_HERE
    Plan Requirement: API access requires a Pro, Studio, or Unlimited subscription, or a one-time 60,000 or 120,000 credit top-up purchase. Rate limits vary by plan: Pro & 60k top-up (60 req/min), Studio & 120k top-up (120 req/min), Unlimited (300 req/min).

    Endpoints

    POST/v1/processProcess an image or video
    GET/v1/accountGet account info and credits
    GET/v1/sessionsList your API sessions
    GET/v1/sessions/:idGet results for a session or a batch
    POST/v1/batchesCreate a CyberBatch (SFTP credentials)
    POST/v1/batches/:id/startStart a CyberBatch with settings
    GET/v1/batchesList batches (filter by sftp_username, status)
    GET/v1/batches/:idBatch status and billing

    POST /v1/process

    Process a single image or video file and receive metadata generated by the CyberStock AI Engine. Provide the file either as a public URL or base64-encoded data.

    Request Parameters
    ParameterTypeDescription
    image_urlrequiredstringPublic URL of the image or video to process. Provide this or image_base64.
    image_base64stringBase64-encoded file data. Use when the file isn't publicly accessible. Limit: 250,000 base64 characters (for larger files, use image_url).
    filenamestringOriginal filename with extension (e.g., "beach.jpg"). Helps AI detect media type.
    languagestringOutput language code. Default: "en". Examples: "es", "de", "fr", "ja".
    keywords_limitnumberMaximum number of keywords to return. Default: 50. Range: 1-50.
    keyword_countnumberExact number of keywords to generate (must be ≤ keywords_limit). Omit to let the engine choose, up to the limit.
    keyword_modestring"single" (default) for single-word keywords, or "long-tail" for multi-word phrases.
    platformstringTarget stock platform. Options: "shutterstock", "adobe", "alamy", "getty", etc.
    media_typestringOverride media type detection: "image", "video", or "vector".
    max_description_charsnumberMaximum characters for the description. Default: 480.
    max_title_charsnumberMaximum characters for the title. Default: 200.
    editorialbooleanEnable editorial mode for news/documentary content. Default: false.
    editorial_seedobjectEditorial context: {"city":"NYC","country":"US","date":"2024-01-15"}
    single_word_onlybooleanForce strictly single-word keywords (no compounds). Default: false.
    allow_compound_keywordsbooleanAllow multi-word compound keywords. Default: false.
    title_casebooleanOutput title in Title Case (capitalize each major word). Default: false (sentence case).
    title_all_capsbooleanOutput title in ALL CAPS. Default: false.
    Response
    JSON
    {
      "ok": true,
      "session_id": "sess_12345",
      "image_id": "img_12345",
      "status": "ready",
      "metadata": {
        "title": "Golden sunset over tropical beach with palm trees",
        "description": "Stunning golden hour sunset illuminating a pristine tropical beach lined with swaying palm trees, calm turquoise waters reflecting warm amber light, creating a serene and idyllic paradise scene perfect for travel and vacation concepts.",
        "keywords": [
          "sunset", "beach", "tropical", "palm trees", "ocean",
          "golden hour", "paradise", "vacation", "travel", "seascape",
          "turquoise", "calm", "serene", "idyllic", "coastline"
        ],
        "category": "Nature/Landscapes",
        "media_type": "image"
      },
      "scores": {
        "selling_score": 87,
        "title_score": 92,
        "description_score": 89,
        "keyword_score": 85
      },
      "credits": {
        "used": true,
        "remaining": 742
      },
      "processing": {
        "duration_ms": 1247,
        "retryable": false
      }
    }

    On Unlimited plans the response also carries a fair_use object.

    Code Examples

    Single File Processing
    bash
    export CYBERSTOCK_API_BASE="https://<your-api-base>/functions/v1/api-gateway"
    
    curl -X POST "$CYBERSTOCK_API_BASE/v1/process" \
      -H "X-API-Key: cs_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "image_url": "https://example.com/photo.jpg",
        "filename": "sunset-beach.jpg",
        "language": "en",
        "keywords_limit": 50,
        "keyword_mode": "single"
      }'
    Batch Processing (Python)
    python
    import requests
    import concurrent.futures
    
    API_KEY = "cs_live_YOUR_KEY"
    API_BASE = "https://<your-api-base>/functions/v1/api-gateway"
    API_URL = f"{API_BASE}/v1/process"
    
    files = [
        {"url": "https://example.com/photo1.jpg", "name": "photo1.jpg"},
        {"url": "https://example.com/photo2.jpg", "name": "photo2.jpg"},
        {"url": "https://example.com/video1.mp4", "name": "video1.mp4"},
    ]
    
    def process_file(file):
        resp = requests.post(API_URL, json={
            "image_url": file["url"],
            "filename": file["name"],
            "language": "en",
            "keywords_limit": 50,
        }, headers={"X-API-Key": API_KEY})
        return resp.json()
    
    # Process up to 5 files concurrently
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        results = list(executor.map(process_file, files))
    
    for r in results:
        if r.get("ok"):
            print(f"{r['metadata']['title']}")

    GET /v1/account

    Check your current plan, credit balance, and account status.

    bash
    curl "$CYBERSTOCK_API_BASE/v1/account" \
      -H "X-API-Key: cs_live_YOUR_KEY"
    JSON
    {
      "ok": true,
      "account": {
        "email": "user@example.com",
        "plan": "pro",
        "api_access": true,
        "api_tier": "pro",
        "credits": 742,
        "credits_subscription": 500,
        "credits_topup": 242,
        "is_unlimited": false
      }
    }

    GET /v1/sessions

    List your recent API processing sessions. Supports pagination via limit and offset (or page).

    bash
    curl "$CYBERSTOCK_API_BASE/v1/sessions?limit=100&offset=0" \
      -H "X-API-Key: cs_live_YOUR_KEY"

    GET /v1/sessions/:id

    Get processed results for a specific session, including full metadata for each file. Supports pagination for large sessions (CyberBatch sessions can hold up to 1 000 000 files).

    bash
    # First page (default limit=100, max=2000)
    curl "$CYBERSTOCK_API_BASE/v1/sessions/<SESSION_ID>?limit=1000&offset=0" \
      -H "X-API-Key: cs_live_YOUR_KEY"
    
    # Loop until pagination.has_more === false
    curl "$CYBERSTOCK_API_BASE/v1/sessions/<SESSION_ID>?limit=1000&offset=1000" \
      -H "X-API-Key: cs_live_YOUR_KEY"
    JSON
    {
      "ok": true,
      "session": { "id": "...", "status": "completed", "total_images": 10219 },
      "pagination": {
        "total": 10219,
        "offset": 0,
        "limit": 1000,
        "returned": 1000,
        "has_more": true,
        "next_offset": 1000
      },
      "results": [ /* 1000 items */ ]
    }
    ParameterTypeDefault / MaxDescription
    limitint100 / 2000Rows to return per call. Values above the ceiling are silently capped.
    offsetint0 / no capNumber of rows to skip. Use pagination.next_offset from the previous response.
    pageint11-indexed alternative to offset. Ignored when offset is set.

    CyberBatch API (1,000+ files)

    For large jobs, CyberBatch processes a whole folder in one go instead of one request per file. You upload over SFTP, start the batch with the same settings you send to /v1/process, and get a webhook when it is done. Results come back through the same GET /v1/sessions/:id endpoint.

    1,000 to 1,000,000 files
    Minimum 1,000 files per batch. For fewer, use /v1/process.
    15% cheaper
    Files paid with credits cost 0.85 credits instead of 1.
    Same output
    Same engine, same settings, same fair-use rules as /v1/process and the web app.

    Billing. On Unlimited, files first use your plan's fair-use allowance, then your credits at the 15% discount, then the fair-use slow lane, exactly like single-file processing. An Unlimited batch always starts. On other plans a batch runs on credits. Files that fail are refunded automatically.

    POST /v1/batches

    Creates a batch and returns SFTP credentials. The password is shown once, and access is valid for 72 hours (see sftp_access_expires_at).

    bash
    curl -X POST "$CYBERSTOCK_API_BASE/v1/batches" \
      -H "X-API-Key: cs_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "name": "Shoot 2026-09-27" }'
    JSON
    {
      "ok": true,
      "batch_id": "6f1c…",
      "sftp": {
        "host": "<sftp host>",
        "port": 2022,
        "protocol": "sftp",
        "username": "cb_vqenvKAO0Cc",
        "password": "shown once — store it now",
        "remote_dir": "/"
      },
      "sftp_access_expires_at": "2026-09-30T10:00:00Z",
      "min_files": 1000,
      "next": "Upload your files over SFTP, then POST /v1/batches/6f1c…/start"
    }
    POST /v1/batches/:id/start

    Starts the batch once your files are uploaded. The body takes the same generation parameters as /v1/process (language, keywords_limit, keyword_count, keyword_mode, platform, max_title_chars, title_case and the rest). Anything you omit uses the same default as /v1/process. Optional: custom_prompt, and notify_email (an address, or false for no email).

    bash
    curl -X POST "$CYBERSTOCK_API_BASE/v1/batches/<BATCH_ID>/start" \
      -H "X-API-Key: cs_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "language": "en",
        "keyword_count": 50,
        "platform": "adobe"
      }'

    Returns 202 with the batch object. Returns 400 BATCH_TOO_SMALL if fewer than 1,000 files are registered, and 409 BATCH_ALREADY_STARTED on a second start.

    GET /v1/batches/:id

    Status, file counts and billing split. status is one of awaiting_start, uploading, queued, submitted, running, aggregating, ready, partial, failed, canceled.

    Before the batch is started, this call registers whatever has finished arriving over SFTP, so files.registered is current right after your transfer (refreshed at most every 20 seconds). A file still being written is counted once its transfer completes, so poll until the number stops changing before you call /start; /start also registers everything that has landed before it checks the minimum. A batch is finished when status is ready, partial or failed and files.queued and files.processing are both 0.

    JSON
    {
      "ok": true,
      "batch": {
        "batch_id": "6f1c…",
        "job_id": "a93e…",
        "status": "running",
        "name": "Shoot 2026-09-27",
        "sftp_username": "cb_vqenvKAO0Cc",
        "files": { "registered": 12000, "processed": 8200, "failed": 0,
                   "queued": 3800, "processing": 0, "awaiting_preview": 0 },
        "billing": {
          "discount_pct": 15,
          "files_from_allowance": 2000,
          "files_on_credits": 10000,
          "files_on_fair_use_slow_lane": 0,
          "credits_charged": 8500,
          "credits_refunded": 0
        },
        "started_at": "2026-09-27T10:12:00Z",
        "completed_at": null,
        "results_path": "/v1/sessions/6f1c…"
      }
    }
    GET /v1/batches

    Lists your batches, newest first, paginated with limit / offset. Filter with sftp_username to find the batch behind an SFTP account, or with status. Batches are not included in GET /v1/sessions, which lists single-file API sessions.

    bash
    curl "$CYBERSTOCK_API_BASE/v1/batches?sftp_username=cb_vqenvKAO0Cc" \
      -H "X-API-Key: cs_live_YOUR_KEY"

    Webhooks

    Set a webhook URL once on the CyberBatch page in the app (Completion webhook card). You get a signing secret and can send a test ping. Every batch, started from the app or the API, then posts batch.completed to your URL when it finishes.

    HTTP
    POST https://your-server.example.com/cyberstock
    Content-Type: application/json
    CyberStock-Event: batch.completed
    CyberStock-Delivery: 2b7d…            (stable across retries: dedupe on it)
    CyberStock-Signature: t=1790000000,v1=5f2a…
    
    {
      "id": "2b7d…",
      "type": "batch.completed",
      "created": "2026-09-27T14:03:11Z",
      "data": {
        "session_id": "6f1c…",
        "job_id": "a93e…",
        "status": "ready",
        "total_files": 12000,
        "processed_files": 12000,
        "failed_files": 0,
        "sftp_username": "cb_vqenvKAO0Cc",
        "session_url": "https://cyberstock.lol/sessions/6f1c…/review-edit",
        "results_path": "/v1/sessions/6f1c…"
      }
    }

    CyberStock-Signature is t=<unix time>,v1=<hex>, where v1 is HMAC-SHA256 of "<t>.<raw body>" with your secret. Verify it against the raw body, before parsing. Reply with any 2xx to acknowledge. Anything else is retried with backoff for about 22 hours.

    python
    import hashlib, hmac
    
    def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
        parts = dict(p.split("=", 1) for p in signature_header.split(","))
        signed = f"{parts['t']}.".encode() + raw_body
        expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
        return hmac.compare_digest(expected, parts["v1"])

    Fair Use (Unlimited)

    On Unlimited plans, /v1/process responses (including 429) and batch start responses carry a fair_use object, so an automation can tell when to top up without watching the app.

    JSON
    "fair_use": {
      "state": "approaching",
      "message": "You are approaching the fair-use threshold of your plan. To keep full speed, credit packs are available.",
      "buy_credits_url": "https://cyberstock.lol/pricing"
    }
    stateMeaning
    okFull speed, nothing to do.
    approachingClose to the fair-use threshold. Still full speed.
    creditsPast the threshold, running at full speed on your credits.
    slowedPast the threshold with no credits: reduced speed. Single-file calls get 429 with retry_after_ms.

    Full Automation Guide

    1. Create an API key in Account → API, and set a webhook URL on the CyberBatch page.
    2. POST /v1/batches and store batch_id and the SFTP password.
    3. Upload 1,000 or more files over SFTP.
    4. POST /v1/batches/:id/start with your generation settings.
    5. Wait for batch.completed (or poll GET /v1/batches/:id every few minutes).
    6. Page through GET /v1/sessions/:id for every file's title, description and keywords.
    python
    """Full CyberBatch automation: create -> SFTP upload -> start -> wait -> results."""
    import os, time, requests, paramiko
    
    API = os.environ["CYBERSTOCK_API_BASE"]
    H = {"X-API-Key": os.environ["CYBERSTOCK_API_KEY"]}
    FOLDER = "/path/to/images"           # 1 000+ JPG / PNG / TIFF / MP4 files
    
    # 1. Create a batch. The SFTP password is returned once.
    b = requests.post(f"{API}/v1/batches", json={"name": "Nightly upload"}, headers=H).json()
    batch_id, s = b["batch_id"], b["sftp"]
    
    # 2. Upload over SFTP.
    t = paramiko.Transport((s["host"], s["port"]))
    t.connect(username=s["username"], password=s["password"])
    sftp = paramiko.SFTPClient.from_transport(t)
    for name in os.listdir(FOLDER):
        sftp.put(os.path.join(FOLDER, name), f"{s['remote_dir'].rstrip('/')}/{name}")
    sftp.close(); t.close()
    
    # 3. Start with the same settings you would send to /v1/process.
    r = requests.post(f"{API}/v1/batches/{batch_id}/start",
                      json={"language": "en", "keyword_count": 50}, headers=H)
    if r.status_code == 400 and r.json()["error"] == "BATCH_TOO_SMALL":
        raise SystemExit(r.json()["message"])   # fewer than 1 000 files registered
    r.raise_for_status()
    
    # 4. Wait. With a webhook configured you can skip this loop entirely and
    #    fetch results when batch.completed arrives.
    while True:
        st = requests.get(f"{API}/v1/batches/{batch_id}", headers=H).json()["batch"]
        if st["status"] in ("ready", "partial", "failed", "canceled"):
            break
        time.sleep(300)
    
    # 5. Results, paginated.
    offset, results = 0, []
    while True:
        page = requests.get(f"{API}/v1/sessions/{batch_id}",
                            params={"limit": 1000, "offset": offset}, headers=H).json()
        results += page["results"]
        if not page["pagination"]["has_more"]:
            break
        offset = page["pagination"]["next_offset"]
    print(len(results), "files keyworded")

    Keep /v1/process for small or urgent jobs. It returns metadata in about a second per file, while a batch completes within 24 hours, usually much sooner.

    Rate Limits

    Rate limits depend on your subscription plan:

    PlanRequests / MinuteMax API Keys
    Pro / 60k Top-Up603
    Studio / 120k Top-Up1203
    Unlimited3003

    When you exceed the limit, the API returns 429 RATE_LIMIT_EXCEEDED. Wait a few seconds and retry.

    Error Reference

    StatusError CodeDescription
    401UNAUTHORIZEDMissing or invalid API key
    401INVALID_API_KEYAPI key is revoked or doesn't exist
    402INSUFFICIENT_CREDITSNot enough credits to process
    403API_ACCESS_DENIEDPlan doesn't include API access (need Pro+)
    403SCOPE_DENIEDAPI key doesn't have required scope
    429RATE_LIMIT_EXCEEDEDToo many requests per minute
    400MISSING_INPUTNo image_url or image_base64 provided
    400INVALID_JSONRequest body is not valid JSON
    502PROCESSING_FAILEDInternal processing error (retry)
    429RATE_LIMITEDFair-use speed limit (Unlimited): wait retry_after_ms, or add credits
    400BATCH_TOO_SMALLFewer than 1,000 files registered for the batch
    404BATCH_NOT_FOUNDBatch doesn't exist or belongs to another account
    409BATCH_ALREADY_STARTEDThe batch was already started

    Ready to get started?

    Generate your API key and start processing files in minutes.