API Integration Docs

Viral Short Analyzer

v1 · stable

Viral Short Analyzer API

Integrasi aplikasi desktop / Windows ke server analisa video viral berbasis Gemini multimodal — AI menonton video langsung, bukan sekadar transcript.

Base URL

auto-detect

Domain terdeteksi otomatis dari URL halaman ini. Aman bila Anda mengganti domain custom nanti.

https://your-domain.com

Viral Short Analyzer — API Integration Guide

Dokumen lengkap untuk integrasi aplikasi desktop (Windows) ke server analisa video viral. Server berbasis TanStack Start + Gemini multimodal (AI menonton video langsung — bukan sekadar transcript).


1. Base URL

Gunakan URL di mana aplikasi web ini di-deploy. Halaman /docs di browser akan otomatis menampilkan Base URL terdeteksi dari domain saat ini.

https://your-domain.com

Contoh production: https://clipmaxs.lovable.app


2. Autentikasi

Semua request ke /api/public/* WAJIB membawa header:

X-App-Key: <APP_API_KEY>

APP_API_KEY di-generate / di-set di halaman /admin → Settings → App API Key. Aplikasi desktop harus menyimpan key ini di config lokal.

Tanpa header / key salah → 401 Unauthorized.


3. Endpoint Ringkasan

MethodPathTujuan
POST/api/public/upload-fileUpload file video lokal → dapat file_uri
POST/api/public/analyzeJalankan analisa (URL / hasil upload) → JSON viral
GET/api/public/progressPantau progress real-time berdasarkan clientJobId

Flow:

[URL YouTube/TikTok/IG] ──► POST /api/public/analyze ──► JSON
                                                    
[File lokal .mp4 dst]   ──► POST /api/public/upload-file ──► file_uri
                              │
                              └─► POST /api/public/analyze (pakai uri) ──► JSON

Untuk URL YouTube/TikTok/Instagram bisa langsung pakai /analyze tanpa upload — server yang menangani download/forward ke Gemini.


4. POST /api/public/upload-file

Upload file video lokal user ke Gemini File API melalui server kami (supaya API key Gemini tidak bocor ke client).

Request

POST https://your-domain.com/api/public/upload-file
Headers:
  X-App-Key: <APP_API_KEY>
  Content-Type: multipart/form-data

Body (form-data):
  file: <binary>   ← required, video (mp4/mov/webm/mkv) max ~1GB

Response 200

{
  "file_uri": "https://generativelanguage.googleapis.com/v1beta/files/abc123",
  "mime_type": "video/mp4",
  "duration_sec": 1234
}

Simpan file_uri dan mime_type untuk dipakai di langkah berikutnya. duration_sec bisa 0 kalau Gemini belum mengembalikan metadata durasi.

Response error

{ "error": "pesan error" }   // status 4xx/5xx

5. POST /api/public/analyze

Endpoint utama. Menerima salah satu dari:

  • url → link YouTube / TikTok / Instagram
  • geminiFileUri → hasil dari /upload-file

Plus objek options yang merefleksikan setting di UI aplikasi desktop.

5.1 Request (JSON — direkomendasikan)

POST https://your-domain.com/api/public/analyze
Headers:
  X-App-Key: <APP_API_KEY>
  Content-Type: application/json

Body:

{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "clientJobId": "job_<guid>",

  "geminiFileUri": null,
  "geminiFileMime": null,
  "geminiFileDurationSec": null,

  "options": {
    "language":        "auto",
    "clipCount":       "5-10",
    "clipDuration":    "30-60",
    "contentFocus":    "auto",
    "visualAi":        false,
    "fullVoNarrative": false,
    "chunkMinutes":    20,
    "autoChunk":       true
  }
}

Kirim salah satu: url ATAU (geminiFileUri + geminiFileMime).

clientJobId opsional tapi sangat direkomendasikan kalau mau polling progress (lihat section 6).

5.2 Request (multipart — kalau mau upload + analyze dalam 1 call)

POST https://your-domain.com/api/public/analyze
Headers:
  X-App-Key: <APP_API_KEY>
  Content-Type: multipart/form-data

Body:
  file:            <binary>   (atau pakai field "url" sebagai gantinya)
  url:             https://...
  clientJobId:     job_<guid>
  language:        auto
  clipCount:       5-10
  clipDuration:    30-60
  contentFocus:    auto
  visualAi:        true       (string "true"/"false" atau "1"/"0")
  fullVoNarrative: false
  chunkMinutes:    20
  autoChunk:       true
  options:         {"language":"id", ...}   ← opsional, JSON string (override field di atas)

5.3 Field options — Mapping ke UI

UI FieldKey JSONTipeNilai yang diterima
AI LANGUAGElanguagestring"auto", "id", "en"
CLIP COUNTclipCountstringbebas, contoh: "3", "5-10", "10+"
CLIP DURATIONclipDurationstringbebas (detik), contoh: "15-30", "30-60", "60-90"
CONTENT FOCUScontentFocusstring"auto", "podcast", "comedy", "motivation", "news", "education", "business", "gaming", "tech", "health", "beauty"
Visual AI (toggle)visualAibooleantrue / false
Full VO NarrativefullVoNarrativebooleantrue / false
Minutes per chunkchunkMinutesnumber5 – 60, default 20 (menit)
Auto chunking (toggle)autoChunkbooleantrue / false, default true

Semua field di options opsional. Jika tidak dikirim:

  • language"auto" (ikut bahasa pembicara)
  • contentFocus"auto"
  • clipCount/clipDuration → tidak ada batasan ketat, AI bebas memilih
  • visualAi/fullVoNarrativefalse
  • chunkMinutes20
  • autoChunktrue

5.4 Efek tiap opsi terhadap output

  • language=id → transcript, title, summary, hook, caption, reason, visual_notes WAJIB Bahasa Indonesia.
  • language=en → idem dalam English.
  • language=auto → AI mendeteksi bahasa dominan pembicara.
  • clipCount="5-10" → AI menghasilkan 5–10 entri viral_moments.
  • clipDuration="30-60"end - start setiap klip berada di rentang 30–60 detik.
  • contentFocus=podcast dll → AI memprioritaskan jenis momen sesuai kategori.
  • visualAi=true → AI memperhatikan ekspresi/gesture/teks di layar; visual_notes lebih detail.
  • fullVoNarrative=truecaption diisi naskah voice-over lengkap siap dibacakan (bukan caption sosmed pendek).
  • chunkMinutes + autoChunk → video panjang diproses per chunk tanpa dipotong fisik; Gemini diberi start_offset/end_offset. Hasil tiap chunk digabung server-side.

5.5 Chunking (video panjang)

Server otomatis membagi video panjang menjadi beberapa segmen virtual:

  • Hanya aktif kalau autoChunk=true dan durasi video > chunkMinutes * 60 + 300 detik.
  • Default chunkMinutes=20, jadi video > ~25 menit akan di-chunk.
  • Setiap chunk diproses dengan key rotation; bila satu key kena timeout (524), server otomatis pindah ke key berikutnya.
  • Output tetap 1 JSON final; chunked dan chunk_count memberitahui apakah proses terjadi per chunk.

6. Response Sukses (200)

{
  "jobId": "f0a9b1cc-...-uuid",
  "result": {
    "source": {
      "type": "youtube",
      "url": "https://www.youtube.com/watch?v=...",
      "duration_sec": 0
    },
    "chunked": false,
    "chunk_count": 1,
    "language": "id",
    "title_suggestion": "Rick Astley - Never Gonna Give You Up",
    "summary": "Video musik klasik ...",
    "transcript": [
      { "start": 0.0,  "end": 4.2, "text": "We're no strangers to love" },
      { "start": 4.2,  "end": 8.5, "text": "You know the rules and so do I" }
    ],
    "viral_moments": [
      {
        "start": 12.0,
        "end": 45.0,
        "score": 0.87,
        "reason": "Hook lirik ikonik + visual khas 80an",
        "hook": "We're no strangers to love",
        "title": "Hook Lirik Rickroll",
        "caption": "POV: ketika kamu di-rickroll lagi 😂 #rickroll",
        "hashtags": ["#rickroll", "#viral", "#fyp", "#80s", "#classic"],
        "visual_notes": "Rick Astley menari di depan panggung dengan jas hitam"
      }
    ]
  }
}

Catatan struktur

  • Semua start / end dalam detik (float), terurut start < end.
  • Server otomatis menukar bila Gemini mengembalikan terbalik dan memastikan urutan key start lebih dulu sebelum end.
  • viral_moments[].hashtags selalu array (boleh kosong).
  • source selalu ada (diinjeksi server, bukan dari Gemini).
  • chunked/chunk_count memberitahui apakah video panjang di-chunk.
  • jobId boleh disimpan untuk lookup ulang di /admin → Jobs.

7. Progress Tracking (polling)

Analisa bisa berjalan menit-an. Aplikasi desktop bisa memantau progress real-time dengan polling endpoint /api/public/progress.

7.1 Kirim clientJobId saat memulai analisa

Generate ID unik di sisi client (mis. GUID / job_<timestamp>_<rand>), lalu sertakan di body request /api/public/analyze:

{
  "url": "https://...",
  "clientJobId": "job_lz9k_8h3a2f",
  "options": { ... }
}

Atau di multipart sebagai field clientJobId.

7.2 Polling progress

GET https://your-domain.com/api/public/progress?clientJobId=<id>
Headers:
  X-App-Key: <APP_API_KEY>

Response 200:

{
  "jobId": "uuid-server",
  "status": "processing",   // processing | done | failed
  "progress": {
    "step": "analyze",       // init | source | metadata | download | upload | prepare | analyze | finalize | done | failed
    "message": "Analisa chunk 2/4 (20.0–40.0 mnt)...",
    "percent": 65,
    "chunk_index": 2,
    "chunk_total": 4,
    "updated_at": "2026-06-29T10:11:12.000Z"
  },
  "error": null,
  "result": null,            // hanya terisi bila status == "done"
  "created_at": "...",
  "finished_at": null
}

Response 404: { "status": "not_found" } — job belum tercatat (tunggu 1–2 detik).

7.3 Pola pemakaian

1. Generate clientJobId
2. POST /api/public/analyze (async di thread/Task)
3. Loop tiap 1–2 detik: GET /api/public/progress?clientJobId=...
   - update UI progress bar dari progress.percent + progress.message
   - stop loop bila status == "done" atau "failed"
4. Ambil hasil final dari response POST /analyze ATAU dari result di /progress

7.4 Daftar step

steparti
initJob baru dibuat
sourceDeteksi sumber (YouTube/TikTok/IG/upload)
metadataAmbil metadata (durasi) dari YouTube
downloadUnduh video dari TikTok/Instagram
uploadUpload ke Gemini File API
prepareSiapkan prompt + chunking
analyzeGemini menganalisa (per chunk)
finalizeGabungkan hasil semua chunk
doneSelesai, result tersedia
failedGagal, lihat error

8. Response Error

HTTPerror (contoh)Penyebab
401UnauthorizedX-App-Key salah / belum di-set di admin
400URL tidak dikenali (YouTube/TikTok/Instagram).Format URL tidak valid
400URL YouTube tidak valid. ...ID YouTube < 11 karakter / link rusak
500Gagal mengunduh video dari tiktok. ...Scraper gagal; tambahkan cookies di /admin
500Output Gemini bukan JSON valid: ...Model meleset; retry biasanya berhasil
500All Gemini keys failedPool API key habis / rate limited; tambah key di admin
524timeout / Cloudflare (rotasi ke key berikutnya)Video panjang / Gemini lambat; coba chunk lebih kecil
500pesan lainLihat tab Jobs di /admin untuk detail

Format selalu:

{ "error": "pesan" }

9. Contoh Implementasi (C# / .NET — aplikasi Windows)

using System.Net.Http.Json;
using System.Text.Json;

public record AnalyzeOptions(
    string language        = "auto",
    string clipCount       = "5-10",
    string clipDuration    = "30-60",
    string contentFocus    = "auto",
    bool   visualAi        = false,
    bool   fullVoNarrative = false,
    int    chunkMinutes    = 20,
    bool   autoChunk       = true
);

public record AnalyzeRequest(
    string? url,
    string? geminiFileUri,
    string? geminiFileMime,
    string? clientJobId,
    AnalyzeOptions options
);

public class ViralClient
{
    private readonly HttpClient _http;
    private readonly string _baseUrl;

    public ViralClient(string baseUrl, string appApiKey)
    {
        _baseUrl = baseUrl.TrimEnd('/');
        _http = new HttpClient { BaseAddress = new Uri(_baseUrl) };
        _http.DefaultRequestHeaders.Add("X-App-Key", appApiKey);
        _http.Timeout = TimeSpan.FromMinutes(15);   // analisa bisa lama
    }

    public async Task<JsonDocument> AnalyzeUrlAsync(string url, AnalyzeOptions opts, string? clientJobId = null)
    {
        var req = new AnalyzeRequest(url, null, null, clientJobId ?? Guid.NewGuid().ToString("N"), opts);
        var res = await _http.PostAsJsonAsync("/api/public/analyze", req);
        var body = await res.Content.ReadAsStringAsync();
        if (!res.IsSuccessStatusCode) throw new Exception(body);
        return JsonDocument.Parse(body);
    }

    public async Task<JsonDocument> AnalyzeFileAsync(string path, AnalyzeOptions opts, string? clientJobId = null)
    {
        // 1) upload
        using var form = new MultipartFormDataContent();
        var fs = File.OpenRead(path);
        form.Add(new StreamContent(fs), "file", Path.GetFileName(path));
        var up = await _http.PostAsync("/api/public/upload-file", form);
        var upBody = await up.Content.ReadAsStringAsync();
        if (!up.IsSuccessStatusCode) throw new Exception(upBody);
        var upJson = JsonDocument.Parse(upBody).RootElement;
        var uri  = upJson.GetProperty("file_uri").GetString();
        var mime = upJson.GetProperty("mime_type").GetString();

        // 2) analyze
        var req = new AnalyzeRequest(null, uri, mime, clientJobId ?? Guid.NewGuid().ToString("N"), opts);
        var res = await _http.PostAsJsonAsync("/api/public/analyze", req);
        var body = await res.Content.ReadAsStringAsync();
        if (!res.IsSuccessStatusCode) throw new Exception(body);
        return JsonDocument.Parse(body);
    }

    public async Task<JsonElement> GetProgressAsync(string clientJobId)
    {
        var res = await _http.GetAsync($"/api/public/progress?clientJobId={clientJobId}");
        var body = await res.Content.ReadAsStringAsync();
        return JsonDocument.Parse(body).RootElement;
    }
}

Cara pakai lengkap dengan polling progress:

var client = new ViralClient("https://clipmaxs.lovable.app", "APP_API_KEY_DARI_ADMIN");
var opts = new AnalyzeOptions(
    language: "id",
    clipCount: "5-10",
    clipDuration: "30-60",
    contentFocus: "podcast",
    visualAi: true,
    fullVoNarrative: false,
    chunkMinutes: 20,
    autoChunk: true);

var clientJobId = $"job_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";

// Jalankan analisa di background
var analyzeTask = Task.Run(async () =>
{
    var json = await client.AnalyzeUrlAsync(
        "https://www.youtube.com/watch?v=dQw4w9WgXcQ", opts, clientJobId);
    return json;
});

// Poll progress setiap 1.5 detik
while (true)
{
    var p = await client.GetProgressAsync(clientJobId);
    var status = p.GetProperty("status").GetString();
    var progress = p.GetProperty("progress");
    var msg = progress.GetProperty("message").GetString();
    var pct = progress.GetProperty("percent").GetInt32();
    Console.WriteLine($"[{status}] {pct}% - {msg}");

    if (status == "done" || status == "failed") break;
    await Task.Delay(1500);
}

var result = await analyzeTask;
Console.WriteLine(result.RootElement.GetProperty("result"));

10. Contoh cURL

URL analyze:

curl -X POST https://your-domain.com/api/public/analyze \
  -H "X-App-Key: APP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "clientJobId": "job_001",
    "options": {
      "language": "id",
      "clipCount": "5-10",
      "clipDuration": "30-60",
      "contentFocus": "podcast",
      "visualAi": true,
      "fullVoNarrative": false,
      "chunkMinutes": 20,
      "autoChunk": true
    }
  }'

Poll progress:

curl -X GET 'https://your-domain.com/api/public/progress?clientJobId=job_001' \
  -H "X-App-Key: APP_API_KEY"

Upload file + analyze:

# 1) upload
RESP=$(curl -s -X POST https://your-domain.com/api/public/upload-file \
  -H "X-App-Key: APP_API_KEY" \
  -F "file=@video.mp4")
URI=$(echo $RESP  | jq -r .file_uri)
MIME=$(echo $RESP | jq -r .mime_type)

# 2) analyze
curl -X POST https://your-domain.com/api/public/analyze \
  -H "X-App-Key: APP_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    "geminiFileUri": "$URI",
    "geminiFileMime": "$MIME",
    "clientJobId": "job_002",
    "options": { "language":"auto", "clipCount":"5-10",
                   "clipDuration":"30-60", "contentFocus":"auto",
                   "visualAi": true, "fullVoNarrative": false,
                   "chunkMinutes": 20, "autoChunk": true }
  }"

11. Timeout, Retry & Concurrency

  • Analisa video panjang bisa 1–10 menit. Set HTTP client timeout min 15 menit.
  • Server otomatis melakukan key rotation kalau satu Gemini key kena rate-limit (429) atau error 5xx/timeout (524/503/504). Aplikasi desktop tidak perlu retry manual.
  • Untuk batch (bulk links), kirim paralel max 2–3 request agar pool API tidak langsung jenuh.
  • Setiap call membuat 1 row di tabel analysis_jobs. Status: processingdone | failed. Hasil JSON tersimpan di kolom result_json dan bisa di-review di /admin → Jobs → Lihat Hasil.

12. Sumber video yang didukung

  • YouTube: youtube.com/watch?v=..., youtu.be/..., youtube.com/shorts/... → Dikirim langsung ke Gemini sebagai file_data.file_uri (Gemini native).
  • TikTok: tiktok.com/@user/video/..., vm.tiktok.com/... → Server mendownload via TikWM/OG tag, lalu upload ke Gemini File API.
  • Instagram: Reels / Post instagram.com/reel/..., /p/... → Server mendownload via OG tag (butuh cookies untuk konten private/age-gated).
  • Upload langsung: mp4 / mov / webm / mkv.

Cookies (Netscape cookies.txt) bisa di-paste di /admin → Cookies untuk membantu scraper Instagram/TikTok ketika konten butuh login.


13. Checklist Antigrafiti (atau dev lain)

Untuk men-generate kode klien, cukup beri tahu:

  1. Base URL dari domain deploy (section 1).
  2. Header auth: X-App-Key: <key>.
  3. 3 endpoint: /api/public/upload-file, /api/public/analyze, /api/public/progress.
  4. Schema request options (section 5.3) — jangan lupa chunkMinutes dan autoChunk.
  5. Schema response (section 6) — JSON dengan jobId, result.viral_moments[], chunked, chunk_count.
  6. Progress tracking via clientJobId + GET /progress (section 7).
  7. Timeout minimum 15 menit; key rotation otomatis untuk 429/5xx.
  8. Error berformat { "error": "..." }.

Dokumen ini sudah self-contained — bisa langsung di-share ke developer aplikasi desktop untuk implementasi.

Butuh bantuan? Buka tab Admin → Jobs untuk detail error setiap request.