Source: docs/integration/prompt-integrations-example-foda.md

Prompt integration example: FODA matrix

End-to-end example of a prompt integration. Shows the manifest, the bundle,

and how the frontend consumes the endpoint.

Use case

An operator creates a prompt integration that generates a FODA (SWOT) matrix

from a business description. The integration accepts one text field and returns

four arrays.

---

Manifest

{
  "version": 1,
  "name": "foda_form",
  "origin": "https://tu-empresa.com",
  "data_classification": "non_sensitive",
  "prompt": "Analizá el contexto de negocio y generá una matriz FODA con 4-8 items por categoría. Cada item debe ser concreto y basado en el input.",
  "input_schema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["business_context"],
    "properties": {
      "business_context": {
        "type": "string",
        "minLength": 20,
        "maxLength": 4000
      }
    }
  },
  "output_schema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["strengths", "weaknesses", "opportunities", "threats"],
    "properties": {
      "strengths":    { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } },
      "weaknesses":   { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } },
      "opportunities": { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } },
      "threats":      { "type": "array", "minItems": 4, "maxItems": 8, "items": { "type": "string", "maxLength": 300 } }
    }
  }
}

Supported schema subset

FeatureSupported
Root type: "object"✅
additionalProperties: false✅
required, properties✅
string with minLength / maxLength✅
array with minItems / maxItems✅
integer / number with bounds✅
boolean✅
Nested objects✅
enum (strings)✅
$ref, oneOf, anyOf, allOf❌

---

Bundle

The Admin API returns this after creating the manifest:

{
  "id": "pi_a1b2c3d4e5f6",
  "revision": 1,
  "status": "active",
  "endpoint": "https://api.forgium.dev/v1/prompt-integrations/pi_pub_x7y8z9w0/execute",
  "origin": "https://tu-empresa.com",
  "turnstile_sitekey": "0x4AAAAAAAxxxxxxxxxxxxxx",
  "turnstile_action": "forgium_prompt_execute",
  "input_schema": {
    "type": "object",
    "additionalProperties": false,
    "required": ["business_context"],
    "properties": {
      "business_context": { "type": "string", "minLength": 20, "maxLength": 4000 }
    }
  }
}

What is NOT in the bundle

FieldWhy
promptRuns server-side only
output_schemaValidated server-side; frontend receives data
turnstile_secretNever leaves the Worker
Admin pathsNot exposed in public docs

---

Frontend integration

1. Load Turnstile

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit" defer></script>

Render the widget with both sitekey and action:

turnstile.render("#container", {
  sitekey: "0x4AAAAAAAxxxxxxxxxxxxxx",
  action: "forgium_prompt_execute",           // mandatory
  callback: (token) => { /* store token */ },
  "expired-callback": () => { /* reset widget */ },
});

The action value comes from the bundle. It is not a secret.

2. Submit

const resp = await fetch("https://api.forgium.dev/v1/prompt-integrations/pi_pub_x7y8z9w0/execute", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    input: { business_context: "..." },
    turnstile_token: token,
  }),
});

const body = await resp.json();

3. Handle response

Success:

{
  "data": {
    "strengths":    ["Item 1", "Item 2", ...],
    "weaknesses":   ["Item 1", "Item 2", ...],
    "opportunities": ["Item 1", "Item 2", ...],
    "threats":      ["Item 1", "Item 2", ...]
  }
}

Error:

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Input does not match schema"
  }
}

4. Error codes

CodeStatusMeaning
ORIGIN_DENIED403Origin does not match manifest
TURNSTILE_INVALID403Token missing, expired, or action mismatch
INTEGRATION_DISABLED404Operator disabled the integration
RATE_LIMITED429Per-IP burst limit (3/min)
DAILY_QUOTA_EXCEEDED429Daily limit (24 successful/day)
REQUEST_TOO_LARGE413Body exceeds limit
VALIDATION_ERROR422Input does not match input_schema
MODEL_OUTPUT_INVALID502Model returned invalid JSON
INFERENCE_UNAVAILABLE503Workers AI unavailable

5. Reset Turnstile after every attempt

Tokens are single-use. Reset the widget after every submission, including

failures, before allowing another attempt.

turnstile.reset(widgetId);

---

Security rules for the frontend

  1. No secrets in client code. The bundle contains only public values.
    Never include the prompt, output schema, API key, or Turnstile secret.
  2. No innerHTML with backend data. Render data fields as text, not HTML.
  3. Validate input client-side. Enforce minLength / maxLength from the
    schema before sending.
  4. Respect Retry-After. For 429 responses, disable submission until the
    retry window passes.

---

Full flow

Browser                              Forgium agent-api
  │                                        │
  │  POST /execute                         │
  │  { input, turnstile_token }            │
  │  Origin: https://tu-empresa.com        │
  │ ─────────────────────────────────────▶ │
  │                                        │
  │                                        │  1. Resolve integration
  │                                        │  2. Check Origin (exact)
  │                                        │  3. Rate limit (3/min per IP)
  │                                        │  4. Validate input schema
  │                                        │  5. Turnstile siteverify
  │                                        │  6. Reserve daily quota
  │                                        │  7. Workers AI (JSON mode)
  │                                        │  8. Validate output schema
  │                                        │
  │  { "data": { ... } }                   │
  │ ◀───────────────────────────────────── │