@@ -83,10 +83,11 @@ export default {
8383 origin ?. startsWith ( 'http://localhost:' ) ; // Allow localhost for dev
8484
8585 // CORS headers
86+ // Include BYOK headers (X-OpenRouter-*) for CLI and programmatic access
8687 const corsHeaders = {
8788 'Access-Control-Allow-Origin' : isAllowedOrigin ? origin : CONFIG . ALLOWED_ORIGIN ,
8889 'Access-Control-Allow-Methods' : 'GET, POST, OPTIONS' ,
89- 'Access-Control-Allow-Headers' : 'Content-Type, X-API-Key' ,
90+ 'Access-Control-Allow-Headers' : 'Content-Type, X-API-Key, X-OpenRouter-Key, X-OpenRouter-Model, X-OpenRouter-Provider, X-OpenRouter-Temperature ' ,
9091 'Access-Control-Allow-Credentials' : 'true' ,
9192 } ;
9293
@@ -277,22 +278,30 @@ async function handleAnnotate(request, env, ctx, corsHeaders, CONFIG) {
277278 } ) ;
278279 }
279280
280- // Verify Turnstile token (required in production)
281- const clientIp = request . headers . get ( 'CF-Connecting-IP' ) ;
282- const turnstileResult = await verifyTurnstileToken (
283- cf_turnstile_response ,
284- env . TURNSTILE_SECRET_KEY ,
285- clientIp
286- ) ;
281+ // Check for BYOK (Bring Your Own Key) mode - CLI/programmatic access with user's own API key
282+ // BYOK users skip Turnstile verification since:
283+ // 1. They can't complete Turnstile challenges (CLI/programmatic access)
284+ // 2. They're using their own API key, so any abuse is on their own account
285+ const isBYOK = request . headers . get ( 'X-OpenRouter-Key' ) !== null ;
287286
288- if ( ! turnstileResult . success ) {
289- return new Response ( JSON . stringify ( {
290- error : 'Bot verification failed' ,
291- details : turnstileResult . error ,
292- } ) , {
293- status : 403 ,
294- headers : { ...corsHeaders , 'Content-Type' : 'application/json' } ,
295- } ) ;
287+ // Verify Turnstile token (required for non-BYOK requests in production)
288+ if ( ! isBYOK ) {
289+ const clientIp = request . headers . get ( 'CF-Connecting-IP' ) ;
290+ const turnstileResult = await verifyTurnstileToken (
291+ cf_turnstile_response ,
292+ env . TURNSTILE_SECRET_KEY ,
293+ clientIp
294+ ) ;
295+
296+ if ( ! turnstileResult . success ) {
297+ return new Response ( JSON . stringify ( {
298+ error : 'Bot verification failed' ,
299+ details : turnstileResult . error ,
300+ } ) , {
301+ status : 403 ,
302+ headers : { ...corsHeaders , 'Content-Type' : 'application/json' } ,
303+ } ) ;
304+ }
296305 }
297306
298307 // Check rate limit
@@ -319,11 +328,20 @@ async function handleAnnotate(request, env, ctx, corsHeaders, CONFIG) {
319328 'Content-Type' : 'application/json' ,
320329 } ;
321330
322- // Add API key if configured
331+ // Add backend API key if configured
323332 if ( env . BACKEND_API_KEY ) {
324333 backendHeaders [ 'X-API-Key' ] = env . BACKEND_API_KEY ;
325334 }
326335
336+ // Forward BYOK headers to backend for user's own API key
337+ const byokHeaders = [ 'X-OpenRouter-Key' , 'X-OpenRouter-Model' , 'X-OpenRouter-Provider' , 'X-OpenRouter-Temperature' ] ;
338+ for ( const header of byokHeaders ) {
339+ const value = request . headers . get ( header ) ;
340+ if ( value ) {
341+ backendHeaders [ header ] = value ;
342+ }
343+ }
344+
327345 // Proxy request to Python backend
328346 const response = await fetch ( `${ backendUrl } /annotate` , {
329347 method : 'POST' ,
@@ -398,34 +416,48 @@ async function handleAnnotateFromImage(request, env, corsHeaders, CONFIG) {
398416 } ) ;
399417 }
400418
401- // Verify Turnstile token (required in production)
402- const clientIp = request . headers . get ( 'CF-Connecting-IP' ) ;
403- const turnstileResult = await verifyTurnstileToken (
404- cf_turnstile_response ,
405- env . TURNSTILE_SECRET_KEY ,
406- clientIp
407- ) ;
419+ // Check for BYOK (Bring Your Own Key) mode - CLI/programmatic access with user's own API key
420+ const isBYOK = request . headers . get ( 'X-OpenRouter-Key' ) !== null ;
408421
409- if ( ! turnstileResult . success ) {
410- return new Response ( JSON . stringify ( {
411- error : 'Bot verification failed' ,
412- details : turnstileResult . error ,
413- } ) , {
414- status : 403 ,
415- headers : { ...corsHeaders , 'Content-Type' : 'application/json' } ,
416- } ) ;
422+ // Verify Turnstile token (required for non-BYOK requests in production)
423+ if ( ! isBYOK ) {
424+ const clientIp = request . headers . get ( 'CF-Connecting-IP' ) ;
425+ const turnstileResult = await verifyTurnstileToken (
426+ cf_turnstile_response ,
427+ env . TURNSTILE_SECRET_KEY ,
428+ clientIp
429+ ) ;
430+
431+ if ( ! turnstileResult . success ) {
432+ return new Response ( JSON . stringify ( {
433+ error : 'Bot verification failed' ,
434+ details : turnstileResult . error ,
435+ } ) , {
436+ status : 403 ,
437+ headers : { ...corsHeaders , 'Content-Type' : 'application/json' } ,
438+ } ) ;
439+ }
417440 }
418441
419442 // Prepare headers for backend request
420443 const backendHeaders = {
421444 'Content-Type' : 'application/json' ,
422445 } ;
423446
424- // Add API key if configured
447+ // Add backend API key if configured
425448 if ( env . BACKEND_API_KEY ) {
426449 backendHeaders [ 'X-API-Key' ] = env . BACKEND_API_KEY ;
427450 }
428451
452+ // Forward BYOK headers to backend for user's own API key
453+ const byokHeaders = [ 'X-OpenRouter-Key' , 'X-OpenRouter-Model' , 'X-OpenRouter-Provider' , 'X-OpenRouter-Temperature' ] ;
454+ for ( const header of byokHeaders ) {
455+ const value = request . headers . get ( header ) ;
456+ if ( value ) {
457+ backendHeaders [ header ] = value ;
458+ }
459+ }
460+
429461 // Proxy request to Python backend
430462 const response = await fetch ( `${ backendUrl } /annotate-from-image` , {
431463 method : 'POST' ,
0 commit comments