Cookbook (Ejemplos prácticos)
Última actualización: 18 de julio de 2026
Muestra diversos escenarios como ScriptDefinition completos. Para el fundamento de la sintaxis, consulta el Catálogo de statements y las Expresiones de valor; para la ejecución y las restricciones, consulta Semántica de ejecución, restricciones y seguridad. En todos los ejemplos, el valor escrito en fields es un mapa de locales ({ "<locale>": valor }), y el locale de los ejemplos se ha unificado en en-US. Los ejemplos que hacen una llamada externa (Http) o que ingieren un archivo Media tienen executionMode en "Async".
Índice
- CRUD básico: 1. Crear y publicar Content · 2. Actualizar con un valor calculado · 3. GET de solo lectura · 4. Leer uno, aplicar un guard y aprobar
- Búsqueda y upsert: 5. slug upsert · 6. Clave de campo dinámica y patch de locale
- API externa: 7. Cobro de crédito por adelantado (CAS), llamada al LLM, reembolso · 8. URL de imagen a Media · 9. imagen base64 a Media · 10. Gestión condicional tras la moderación · 11. try/catch fallback
- Paralelo: 12. Combinar tras llamadas en paralelo · 13. Revisión de registro
- Bucles y agregación: 14. Crear N desde una entrada de array · 15. Sembrar con counted loop · 16. Eliminación en cascada · 17. Suma acumulada en bucle · 18. Recorrer toda la paginación · 19. Recopilación por lotes de ids
- Saga y concurrencia: 20. Saga de pago · 21. CAS con bloqueo optimista
CRUD básico
1. Crear y publicar Content
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"fields": { "title": { "en-US": "{ /payload/fields/title }" }, "body": { "en-US": "{ /payload/fields/body }" } },
"publish": true, "name": "post" },
{ "type": "Return", "value": { "id": "{ /post/sys/id }" }, "statusCode": 201 } ] }2. Actualizar con un valor calculado (recuento de vistas +1)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "viewCount": { "en-US": { "+": [ "{ /payload/fields/viewCount }", 1 ] } } } } ] }3. GET de solo lectura: mi lista de pedidos
{ "method": "Get", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"where": { "createdBy": ":self" }, "order": "-sys.createdAt", "limit": 20, "name": "orders" },
{ "type": "Return", "value": { "orders": "{ /orders/items }", "next": "{ /orders/next }" } } ] }Script no solo sirve para escribir, sino también como endpoint BFF de lectura. Con createdBy: ":self" consulta "solo lo mío" y devuelve el resultado tal cual.
4. Leer uno, aplicar un guard y aprobar
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceRead", "resource": "Content", "target": { "sys": { "id": "{ /payload/fields/orderId }" } }, "name": "order" },
{ "type": "If", "condition": { "!=": [ "{ /order/fields/status/en-US }", "pending" ] },
"then": [ { "type": "Return", "value": { "reason": "not pending" }, "isError": true, "statusCode": 409 } ] },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "approved" } }, "publish": true },
{ "type": "Return", "value": { "ok": true } } ] }Al vincular un único elemento a un nombre con ResourceRead, lo referencias directamente como { /order/fields/... } (sin necesidad de items/0). Si no existe, la lectura da error (puedes envolverla en Try).
Búsqueda y upsert
5. slug upsert (find-then-upsert)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"where": { "fields.slug": { "eq": "{ /payload/fields/slug }" } }, "name": "found" },
{ "type": "If", "condition": { "!!": "{ /found/sys/id }" },
"then": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /found/sys/id }" } },
"fields": { "body": { "en-US": "{ /payload/fields/body }" } } },
{ "type": "Return", "value": { "id": "{ /found/sys/id }", "op": "updated" } } ],
"else": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_article" } },
"fields": { "slug": { "en-US": "{ /payload/fields/slug }" }, "body": { "en-US": "{ /payload/fields/body }" } }, "name": "created" },
{ "type": "Return", "value": { "id": "{ /created/sys/id }", "op": "created" }, "statusCode": 201 } ] } ] }ResourceFind vincula directamente la primera coincidencia (o null si no hay ninguna), y { "!!": "{ /found/sys/id }" } bifurca según si existe.
6. Clave de campo dinámica y patch de locale dinámico
{ "method": "Patch", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } },
"fields": { "{ /payload/fields/fieldKey }": { "{ /payload/fields/locale }": "{ /payload/fields/value }" } } } ] }Tanto la clave del campo como la clave del bucket de locale son referencias { /ptr }. Úsalo cuando quieras colocar una traducción en un bucket de locale concreto.
API externa
7. Guard de crédito, cobro por adelantado (CAS), llamada al LLM, reembolso en caso de fallo (ejemplo destacado)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_wallet" } },
"where": { "createdBy": ":self" }, "name": "wallet" },
{ "type": "If", "condition": { "<": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] },
"then": [ { "type": "Return", "value": { "ok": false, "reason": "insufficient credit" }, "isError": true, "statusCode": 402 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"version": "{ /wallet/sys/version }",
"fields": { "balance": { "en-US": { "-": [ "{ /wallet/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } },
"name": "charged" } ],
"catch": [ { "type": "Return", "value": { "ok": false, "reason": "version conflict, reintentar" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.llm.com/v1/gen",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 15000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/choices/0/message/content }" } }, "name": "out" },
{ "type": "Return", "value": { "ok": true, "id": "{ /out/sys/id }", "remaining": "{ /charged/fields/balance/en-US }" } } ],
"catch": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /wallet/sys/id }" } },
"fields": { "balance": { "en-US": { "+": [ "{ /charged/fields/balance/en-US }", "{ /payload/fields/cost }" ] } } } },
{ "type": "Return", "value": { "ok": false, "reason": "generation failed, refunded" }, "isError": true, "statusCode": 502 } ] } ] }Primero, un guard comprueba si el saldo es suficiente y luego descuenta antes de la llamada externa. El descuento aplica un bloqueo optimista (CAS) sobre el sys.version de la wallet. Si otra ejecución cambió la wallet entre la lectura del saldo y el descuento, aborta por discrepancia de versión y catch devuelve 409. Como no se ha hecho ninguna llamada externa, las solicitudes concurrentes no se descuentan por duplicado. Solo después de confirmar el descuento se llama al LLM y, si esa llamada falla, catch vuelve a sumar el importe descontado (cost) para emitir un reembolso (compensación) y luego devuelve 502. El orden consiste en confirmar el cargo antes de la llamada externa irreversible y compensar solo en caso de fallo. La clave secreta se coloca en una cabecera secret:true. Para conocer los límites de la compensación, consulta Sin transacciones y compensación en Semántica de ejecución.
8. Convertir una imagen (URL) en Media y adjuntarla a Content
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "title": { "en-US": "{ /payload/fields/prompt }" },
"file": { "en-US": { "source": "{ /gen/body/data/0/url }", "encoding": "url" } } }, "name": "img" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_artwork" } },
"fields": { "prompt": { "en-US": "{ /payload/fields/prompt }" }, "image": { "en-US": "{ /img/sys/id }" } } } ] }Se crea el Media con un name y ResourceCreate (Content) coloca { /img/sys/id } en el campo de referencia. Media usa el mismo modelo de fields que Content. file es la directiva de ingesta { source, encoding }. Como es una ingesta de archivo, es Async.
9. Imagen base64 a Media
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.img.com/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "name": "gen" },
{ "type": "ResourceCreate", "resource": "Media",
"fields": { "file": { "en-US": { "source": "{ /gen/body/data/0/b64_json }", "encoding": "base64" } } } } ] }10. Publicación o eliminación condicional tras la moderación
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Http", "method": "POST", "url": "https://api.mod.com/check",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "text": "{ /payload/fields/body }" }, "name": "mod" },
{ "type": "If", "condition": { "==": [ "{ /mod/body/flagged }", true ] },
"then": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ],
"else": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] } ] }11. try/catch: fallback en caso de fallo externo
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://primary.api/gen",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "prompt": "{ /payload/fields/prompt }" }, "timeoutMs": 8000, "name": "resp" },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "{ /resp/body/text }" }, "source": { "en-US": "primary" } } } ],
"catch": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_result" } },
"fields": { "text": { "en-US": "Generación fallida" }, "error": { "en-US": "{ /error/message }" }, "source": { "en-US": "fallback" } } } ] } ] }Paralelo
12. Combinar dos llamadas externas paralelas en un Content
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "GET", "url": "https://api.a.com/x", "name": "a" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.b.com/y", "name": "b" } ] ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_merged" } },
"fields": { "left": { "en-US": "{ /a/body/value }" }, "right": { "en-US": "{ /b/body/value }" } } } ] }13. Revisión de registro: puntuaciones en paralelo seguidas de una decisión basada en and
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "Parallel", "branches": [
[ { "type": "Http", "method": "POST", "url": "https://api.fraud.com/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ],
"body": { "email": "{ /payload/fields/email }" }, "name": "fraud" } ],
[ { "type": "Http", "method": "GET", "url": "https://api.credit.com/v1/{ /payload/fields/userId }/score",
"headers": [ { "key": "x-api-key", "value": "...", "secret": true } ], "name": "credit" } ] ] },
{ "type": "If",
"condition": { "and": [ { "<": [ "{ /fraud/body/risk }", 0.5 ] }, { ">=": [ "{ /credit/body/score }", 700 ] } ] },
"then": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"fields": { "email": { "en-US": "{ /payload/fields/email }" }, "status": { "en-US": "approved" } } },
{ "type": "Return", "value": { "decision": "approved" }, "statusCode": 201 } ],
"else": [ { "type": "Return", "value": { "decision": "manual-review" }, "statusCode": 202 } ] } ] }También puedes insertar { /ptr } en la ruta de la URL. Los resultados de las ramas se referencian tras la unión. Hay dos llamadas externas (tres como máximo).
Bucles y agregación
14. N Content a partir de una entrada de array (Loop over)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "item", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_item" } },
"fields": { "name": { "en-US": "{ /item/name }" }, "qty": { "en-US": "{ /item/qty }" } } } ] } ] }15. counted loop (for): sembrar slots
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "Loop", "for": { "from": 1, "to": 5 }, "as": "i", "maxIterations": 100,
"body": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_slot" } },
"fields": { "index": { "en-US": "{ /i }" }, "status": { "en-US": "open" } } } ] } ] }for abarca de from a to, ambos incluidos (literales enteros, step por defecto 1). as vincula el contador actual a { /i }.
16. Eliminación en cascada (PageRead, Loop, Delete)
{ "method": "Delete", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_comment" } },
"where": { "fields.postId": { "eq": "{ /payload/sys/id }" } }, "limit": 100, "name": "comments" },
{ "type": "Loop", "over": "{ /comments/items }", "as": "c", "maxIterations": 100,
"body": [ { "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /c/sys/id }" } } } ] },
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /payload/sys/id }" } } } ] }Si supera los 100, gestiónalo con 18. Recorrer toda la paginación.
17. Acumulación en bucle: suma con SetVar
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "total", "value": 0 },
{ "type": "Loop", "over": "{ /payload/fields/items }", "as": "row", "maxIterations": 100,
"body": [ { "type": "SetVar", "var": "total", "value": { "+": [ "{ /vars/total }", "{ /row/qty }" ] } } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_summary" } },
"fields": { "totalQty": { "en-US": "{ /vars/total }" } } } ] }18. Recorrer toda la paginación
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "cursor", "value": null },
{ "type": "SetVar", "var": "hasMore", "value": true },
{ "type": "Loop", "while": "{ /vars/hasMore }", "maxIterations": 1000,
"body": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_post" } },
"where": { "fields.status": { "eq": "draft" } }, "limit": 100, "cursor": "{ /vars/cursor }", "name": "page" },
{ "type": "Loop", "over": "{ /page/items }", "as": "p", "maxIterations": 100,
"body": [ { "type": "ResourcePublish", "resource": "Content", "target": { "sys": { "id": "{ /p/sys/id }" } } } ] },
{ "type": "SetVar", "var": "cursor", "value": "{ /page/next }" },
{ "type": "SetVar", "var": "hasMore", "value": { "!!": "{ /page/next }" } } ] } ] }cursor, while y SetVar recorren todas las páginas. Como las llamadas externas están prohibidas dentro del body de un Loop, aquí solo se usan operaciones de recursos.
19. Recopilar ids por lotes desde una lista de correos electrónicos (merge)
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "SetVar", "var": "ids", "value": [] },
{ "type": "SetVar", "var": "missing", "value": [] },
{ "type": "Loop", "over": "{ /payload/fields/emails }", "as": "email", "maxIterations": 100,
"body": [
{ "type": "ResourceFind", "resource": "Content", "contentType": { "sys": { "id": "ct_account" } },
"where": { "fields.email": { "eq": "{ /email }" } }, "name": "acc" },
{ "type": "If", "condition": { "!!": "{ /acc/sys/id }" },
"then": [ { "type": "SetVar", "var": "ids", "value": { "merge": [ "{ /vars/ids }", [ "{ /acc/sys/id }" ] ] } } ],
"else": [ { "type": "SetVar", "var": "missing", "value": { "merge": [ "{ /vars/missing }", [ "{ /email }" ] ] } } ] } ] },
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_campaign" } },
"fields": { "recipients": { "en-US": "{ /vars/ids }" }, "unresolved": { "en-US": "{ /vars/missing }" } } } ] }Una lectura (ResourceFind) no es una llamada externa, por lo que se permite dentro del body de un Loop. La presencia y la ausencia se acumulan por separado con merge.
Saga y concurrencia
20. Saga de pago (Try/catch/finally)
{ "method": "Post", "executionMode": "Async",
"statements": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_order" } },
"fields": { "sku": { "en-US": "{ /payload/fields/sku }" }, "status": { "en-US": "reserved" } },
"publish": false, "name": "order" },
{ "type": "Try",
"body": [
{ "type": "Http", "method": "POST", "url": "https://api.pay.com/charge",
"headers": [ { "key": "Authorization", "value": "Bearer sk-...", "secret": true } ],
"body": { "amount": "{ /payload/fields/amount }", "ref": "{ /order/sys/id }" }, "timeoutMs": 10000, "name": "pay" },
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } },
"fields": { "status": { "en-US": "paid" }, "txId": { "en-US": "{ /pay/body/transactionId }" } }, "publish": true },
{ "type": "Return", "value": { "orderId": "{ /order/sys/id }", "status": "paid" }, "statusCode": 201 } ],
"catch": [
{ "type": "ResourceDelete", "resource": "Content", "target": { "sys": { "id": "{ /order/sys/id }" } } },
{ "type": "Return", "value": { "reason": "payment failed", "detail": "{ /error/message }" }, "isError": true, "statusCode": 402 } ],
"finally": [
{ "type": "ResourceCreate", "resource": "Content", "contentType": { "sys": { "id": "ct_paylog" } },
"fields": { "orderRef": { "en-US": "{ /order/sys/id }" }, "amount": { "en-US": "{ /payload/fields/amount }" } } } ] } ] }Tras la reserva (draft): si el pago tiene éxito, se confirma, se publica y se devuelve 201; si falla, catch elimina la reserva (compensación) y devuelve 402; finally registra siempre. La compensación basada en eliminación produce un nuevo sys.id, por lo que entra dentro de la limitación en la que las referencias se rompen (consulta Sin transacciones y compensación en Semántica de ejecución).
21. CAS con bloqueo optimista
{ "method": "Post", "executionMode": "Sync",
"statements": [
{ "type": "ResourcePageRead", "resource": "Content", "contentType": { "sys": { "id": "ct_stock" } },
"where": { "fields.sku": { "eq": "{ /payload/fields/sku }" } }, "limit": 1, "name": "stock" },
{ "type": "If", "condition": { "<": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] },
"then": [ { "type": "Return", "value": { "reason": "out of stock" }, "isError": true, "statusCode": 409 } ] },
{ "type": "Try",
"body": [
{ "type": "ResourcePatch", "resource": "Content", "target": { "sys": { "id": "{ /stock/items/0/sys/id }" } },
"version": "{ /stock/items/0/sys/version }",
"fields": { "qty": { "en-US": { "-": [ "{ /stock/items/0/fields/qty/en-US }", "{ /payload/fields/amount }" ] } } } },
{ "type": "Return", "value": { "ok": true } } ],
"catch": [
{ "type": "Return", "value": { "reason": "version conflict, reintentar" }, "isError": true, "statusCode": 409 } ] } ] }Se lee el stock para obtener un sys.version reciente y luego se descuenta con esa versión (version). Si otra ejecución cambió el valor entre la lectura y la escritura, aborta por discrepancia de versión y catch devuelve 409. El guard de falta de stock queda fuera de Try (una salida anticipada normal).
Documentos relacionados
- Catálogo de statements: los campos y resultados de los statements usados en los ejemplos.
- Expresiones de valor: referencias, JsonLogic y reglas de mapas de locales.
- Semántica de ejecución, restricciones y seguridad: orden de ejecución, compensación, bloqueo optimista y restricciones.
- Descripción general de Script: la estructura de nivel superior y los modos de ejecución.
