feat: migrate hosted API auth to TALOCODE_API_KEY - #53
Conversation
- Add TALOCODE_API_KEY as primary auth (CLIPLOGOOP_API_KEY deprecated fallback) - Create hosted API routes under /v1/cliploop/* (brief, script, render, campaign) - Add Talocode Cloud billing integration (charge before execution) - Add talocode-auth module (validate, extract, fallback) - Add talocode-billing module (POST charge endpoint) - Add route handler utility with auth + billing middleware - Update gateway config for Talocode Cloud auth - Update .env.example with TALOCODE_API_KEY docs - Add auth tests (valid, legacy, reject, extract, key redaction)
- Auth module (talocode-auth.ts): prefer TALOCODE_API_KEY, accept CLIPLOOP_API_KEY as deprecated fallback, deprecation warning emitted - Route handler (talocode-route-handler.ts): deprecation warning for legacy key, improved billing metadata with mode: 'hosted' - Billing (talocode-billing.ts): default base URL now api.talocode.site - Env schema (env/index.ts): TALOCODE_API_KEY, TALOCODE_BASE_URL, CLIPLOOP_API_KEY added to Zod validation - 6 hosted routes: POST /v1/cliploop/brief/generate, script/generate, video/render, campaign/create, campaign/package, GET health - Each route: input validation, auth via handleRoute, billing charge via Talocode Cloud, structured JSON errors on 401/402/502 - SDK (cliploop-sdk): prefers TALOCODE_API_KEY, adds 5 cloud methods (generateBrief, generateScript, renderVideo, createCampaign, packageCampaign), deprecation warning for CLIPLOOP_API_KEY - .env.example: TALOCODE_BASE_URL=https://api.talocode.site, CLIPLOOP_API_KEY marked deprecated - Tests: 24 tests (auth precedence, extraction, validation, redaction, route handler auth failures, deprecation warnings, billing errors) - Docs: cliploop-sdk README, docs-site HTML pages, talocode/CLOUD.md, talocode/docs/api.md, talocode/docs/pricing.md updated
❌ Deploy Preview for cliploop failed.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89546488d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| async generateBrief( | ||
| input: { prompt: string; channel?: string; tone?: string; duration?: number; cta?: string }, | ||
| options: ClipLoopRequestOptions = {} | ||
| ): Promise<Record<string, unknown>> { |
There was a problem hiding this comment.
Keep hosted SDK methods inside the client class
The ClipLoopClient class is closed just before these new methods, so async generateBrief(...) and the rest are parsed as top-level method declarations rather than class members. This makes @cliploop/sdk fail to build/import and leaves callers without the hosted API methods.
Useful? React with 👍 / 👎.
| const response = await fetch( | ||
| `${this.talocodeBaseURL}/v1/cliploop/brief/generate`, | ||
| { |
There was a problem hiding this comment.
Match SDK URLs to the committed Next routes
The SDK calls /v1/cliploop/..., but the new route files in this repo live under app/api/v1/... and src/app/api/v1/..., which Next exposes as /api/v1/...; next.config.ts only excludes api/ paths from the SPA rewrite, so /v1/cliploop/brief/generate resolves as a non-API page in this app. As committed, every new hosted SDK method targets the wrong path unless an external proxy rewrites it.
Useful? React with 👍 / 👎.
| requestId, | ||
| idempotencyKey: requestId, | ||
| metadata: { |
There was a problem hiding this comment.
Honor client idempotency keys for billing
This always sends the locally generated requestId as the billing idempotency key instead of the request's Idempotency-Key header that the SDK sets. Retrying the same request with the same client idempotency key will therefore produce a new billing idempotency key and can charge credits again.
Useful? React with 👍 / 👎.
| if (!prompt || typeof prompt !== 'string') { | ||
| return Response.json( | ||
| { ok: false, error: { code: 'validation_error', message: 'prompt is required and must be a string.' } }, | ||
| { status: 400 }, | ||
| ) |
There was a problem hiding this comment.
Move validation ahead of the credit charge
Because this validation runs inside the handleRoute callback, handleRoute has already charged 15 credits before a missing or non-string prompt returns this 400. The same wrapper pattern is used by the other new hosted endpoints, so malformed requests with valid auth can consume credits without any work being performed.
Useful? React with 👍 / 👎.
| if (cliploopKey && incomingKey === cliploopKey) { | ||
| return { valid: true, keyType: 'cliploop_legacy' } | ||
| } |
There was a problem hiding this comment.
Reject legacy keys once a Talocode key is configured
When both TALOCODE_API_KEY and the deprecated CLIPLOOP_API_KEY remain in the environment during rotation, this fallback still accepts the old ClipLoop key even though the function contract says the legacy key is only accepted when it is the only configured key. That leaves the old secret usable after migration until someone also removes the legacy env var.
Useful? React with 👍 / 👎.
| @@ -0,0 +1,21 @@ | |||
| import { handleRoute } from '@/lib/talocode-route-handler' | |||
There was a problem hiding this comment.
Do not add a root app directory beside src/app
This creates a root-level app/ directory in a project whose real routes live under src/app; Next documents that src/app is ignored when a root app directory is present (https://nextjs.org/docs/app/api-reference/file-conventions/src-folder). In this context the existing pages and public API routes under src/app would drop out of routing, leaving only these new root routes active.
Useful? React with 👍 / 👎.
| <div class="content"> | ||
|
|
||
| <h1>Examples</h1> | ||
| <h1>Examples</h1> |
There was a problem hiding this comment.
Restore the generated examples page shell
The generated docs-site/dist/examples/index.html was replaced with only the inner fragment, unlike the other files under docs-site/dist that include the full document shell. If this dist directory is deployed, the Examples page loses its doctype, stylesheet, sidebar navigation, footer, and script.
Useful? React with 👍 / 👎.
| if (process.env.TALOCODE_API_KEY) { | ||
| return process.env.TALOCODE_API_KEY; |
There was a problem hiding this comment.
Keep Weekly Promo using ClipLoop API keys
This preference makes new ClipLoopClient().generateWeeklyPromo() send TALOCODE_API_KEY to https://app.cliploop.site/api/public/weekly-promo. I checked that route still authenticates through requireApiKeyIdentity()/authenticateApiKey() against ClipLoop's API-key table, not the Talocode env key, so users who set the new env var, or both vars during migration, will get 401s for the existing Weekly Promo API.
Useful? React with 👍 / 👎.
| body: JSON.stringify(input), | ||
| } | ||
| ) | ||
| const data = await response.json() |
There was a problem hiding this comment.
Parse hosted API errors before JSON decoding
Each new hosted SDK method calls response.json() before checking response.ok. When a gateway or CDN returns an empty or HTML error body for cases like 401/502/504, callers get a raw SyntaxError instead of a ClipLoopApiError with the HTTP status and body, so they cannot handle hosted API failures consistently.
Useful? React with 👍 / 👎.
Central engine module (src/lib/cliploop-cloud-engine.ts):
- generateBrief: LLM-driven brief with deterministic mock fallback
- generateScript: LLM-driven script with scenes, caption, hashtags
- renderVideo: returns 'queued' when provider configured, 'provider_required' when not
- createCampaign: campaign object with draft status
- packageCampaign: package object with delivery summary
Route updates (5 POST routes):
- Each validates input with Zod schema
- Calls handleRoute for auth + billing before engine execution
- Returns structured JSON: { id, object, result, usage }
- Video render includes warnings array when provider_required
Env schema:
- Added CLIPLOOP_PROVIDER, CLIPLOOP_RENDER_PROVIDER,
REMOTION_RENDER_ENABLED, STORAGE_BUCKET, STORAGE_PUBLIC_URL
- isMockMode() reads from process.env with env fallback
Tests (28 total, 27 passing):
- Engine: schema validation, deterministic mock output, render
provider_required/queued states, campaign/package objects
- Auth/billing: engine NOT called on missing auth, invalid auth,
or billing failure (3 new tests)
- Pre-existing ARM64/module resolution blocks engine test runtime
(zod not loadable from this env), verified structurally
Deterministic fallback:
- Brief/script/campaign/package return structured data without LLM
- Video render returns 'provider_required' when no render provider
Migrates ClipLoop hosted API access to TALOCODE_API_KEY as the primary key, keeps CLIPLOOP_API_KEY as a deprecated fallback, adds Talocode Cloud billing integration, hosted /v1/cliploop/* route surface, SDK updates, docs updates, and tests.
Notes:
Validation: