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
| Feature | Supported |
|---|---|
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
| Field | Why |
|---|---|
prompt | Runs server-side only |
output_schema | Validated server-side; frontend receives data |
turnstile_secret | Never leaves the Worker |
| Admin paths | Not 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
| Code | Status | Meaning |
|---|---|---|
ORIGIN_DENIED | 403 | Origin does not match manifest |
TURNSTILE_INVALID | 403 | Token missing, expired, or action mismatch |
INTEGRATION_DISABLED | 404 | Operator disabled the integration |
RATE_LIMITED | 429 | Per-IP burst limit (3/min) |
DAILY_QUOTA_EXCEEDED | 429 | Daily limit (24 successful/day) |
REQUEST_TOO_LARGE | 413 | Body exceeds limit |
VALIDATION_ERROR | 422 | Input does not match input_schema |
MODEL_OUTPUT_INVALID | 502 | Model returned invalid JSON |
INFERENCE_UNAVAILABLE | 503 | Workers 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
- No secrets in client code. The bundle contains only public values.
Never include the prompt, output schema, API key, or Turnstile secret. - No innerHTML with backend data. Render
datafields as text, not HTML. - Validate input client-side. Enforce
minLength/maxLengthfrom the
schema before sending. - 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": { ... } } │
│ ◀───────────────────────────────────── │