{
  "slug": "call-openai-from-weweb-securely",
  "title": "Call OpenAI API from WeWeb Securely",
  "metaDescription": "Call OpenAI from a WeWeb app without publishing your API key. RealComposer holds OPENAI_API_KEY in an edge vault and enforces allowed origins per request.",
  "headline": "AI features in WeWeb without an OpenAI key in the front end",
  "category": "security",
  "categoryLabel": "Security & Secret Vault",
  "urgency": 10,
  "clientPlatform": "WeWeb",
  "upstreamServices": [
    "OpenAI"
  ],
  "searchIntentQuery": "call openai api from weweb without exposing api key",
  "directAnswerSummary": "An OpenAI key pasted into a WeWeb REST action is published with the app and readable in DevTools. Send WeWeb requests to a RealComposer pipeline instead. The edge injects OPENAI_API_KEY from the vault, checks the calling origin, and returns only the assistant message text.",
  "problem": {
    "title": "Why WeWeb REST actions expose OpenAI keys",
    "explanation": "WeWeb builds a front-end app, so workflow actions and collection headers ship to every visitor. The WeWeb CORS proxy changes where the request is sent from, but the Authorization header is still defined in published app configuration.",
    "risks": [
      "OpenAI key scraped from the published WeWeb bundle",
      "Unbounded token spend from bots replaying the endpoint",
      "Full completion payloads with usage data sent to the client"
    ]
  },
  "solutionSteps": [
    {
      "stepNumber": 1,
      "title": "Store credentials in Secret Vault",
      "description": "Save OPENAI_API_KEY in RealComposer so the value never enters the WeWeb project."
    },
    {
      "stepNumber": 2,
      "title": "Configure the edge pipeline",
      "description": "Add the upstream steps with vault header injection, set allowed origins, and map only the fields the screen binds."
    },
    {
      "stepNumber": 3,
      "title": "Call the pipeline from a WeWeb workflow",
      "description": "Call your workspace edge URL with a restricted rc_live_ client token.",
      "codeSnippet": "// WeWeb workflow: REST API request action\nURL:     https://realcomposer.com/v1/workspace/call-openai-from-weweb-securely\nMethod:  POST\nHeaders: Authorization: Bearer rc_live_client_token\nBody:    { \"messages\": [{ \"role\": \"user\", \"content\": variables.prompt }] }"
    }
  ],
  "pipelineConfig": {
    "name": "Call OpenAI API from WeWeb Securely",
    "method": "POST",
    "cacheTtlSeconds": 0,
    "authMode": "client_token",
    "steps": [
      {
        "stepKey": "openai",
        "upstreamUrlTemplate": "https://api.openai.com/v1/chat/completions",
        "httpMethod": "POST",
        "headersTemplate": {
          "Authorization": "Bearer {{secrets.OPENAI_API_KEY}}",
          "Content-Type": "application/json"
        },
        "timeoutMs": 15000
      }
    ],
    "outputMapping": {
      "version": 1,
      "fields": [
        {
          "targetPath": "reply",
          "sourceStep": "openai",
          "sourcePath": "choices.0.message.content"
        },
        {
          "targetPath": "tokens",
          "sourceStep": "openai",
          "sourcePath": "usage.total_tokens",
          "type": "number"
        }
      ]
    }
  },
  "sampleBloatedInput": {
    "id": "chatcmpl-9x",
    "object": "chat.completion",
    "model": "gpt-4o-mini",
    "system_fingerprint": "fp_44709d6fcb",
    "choices": [
      {
        "index": 0,
        "finish_reason": "stop",
        "message": {
          "role": "assistant",
          "content": "Here is your summary."
        },
        "logprobs": null
      }
    ],
    "usage": {
      "prompt_tokens": 212,
      "completion_tokens": 48,
      "total_tokens": 260
    }
  },
  "sampleSlimmedOutput": {
    "reply": "Here is your summary.",
    "tokens": 260
  },
  "curlSnippet": "curl -X POST \"https://realcomposer.com/v1/workspace/call-openai-from-weweb-securely\" \\\n  -H \"Authorization: Bearer rc_live_client_token\"",
  "faqs": [
    {
      "question": "Does the WeWeb server proxy hide my OpenAI key?",
      "answer": "No. It helps with CORS, but headers defined in the app are still part of the published configuration. Keep the key in a vault behind your own endpoint."
    },
    {
      "question": "How do I stop abuse of the AI endpoint?",
      "answer": "Set allowed origins to your WeWeb domains and apply per-client rate limits on the RealComposer pipeline."
    }
  ],
  "relatedRecipeSlugs": [
    "hide-openai-api-key-react",
    "weweb-multiple-apis-page-load",
    "fix-cors-error-weweb"
  ],
  "links": {
    "page": "https://realcomposer.com/recipes/call-openai-from-weweb-securely",
    "markdown": "https://realcomposer.com/recipes/call-openai-from-weweb-securely.md",
    "json": "https://realcomposer.com/recipes/call-openai-from-weweb-securely.json",
    "install": "https://realcomposer.com/dashboard/pipelines/new?recipe=call-openai-from-weweb-securely&via=agent"
  }
}