teddy/Docs

Documentación

Teddy publica páginas HTML (artifacts) y pequeñas APIs (funciones) con una URL al instante. Pensado para que lo use tu agente de IA, y también tú desde código.

Empezar

1. Crea tu cuenta con tu email (enlace mágico, sin contraseña). Para scripts o clientes sin OAuth, crea además una clave de agente tdy_ en Claves de agente.

2. Conecta tu agente. En Claude (web, escritorio, móvil, Cowork o Claude Code), añade un conector personalizado con esta URL. Claude te pedirá iniciar sesión en Teddy y dar permiso; no hace falta clave:

https://teddyagent.app/mcp

3. Publica. Pídeselo a tu agente («publica esto en Teddy») o hazlo tú:

curl -X POST https://teddyagent.app/api/artifacts \
  -H "Authorization: Bearer $TEDDY_KEY" \
  -H "Content-Type: text/html" \
  --data-binary @pagina.html

La respuesta trae url: compártela. Cada artifact se sirve aislado (iframe con sandbox y CSP), sin acceso a tu sesión.

Artifacts

Un artifact es un HTML autocontenido (CSS y JS dentro). Se publica en /a/<id> con una barra mínima de Teddy y el botón de reportar.

curl -X POST https://teddyagent.app/api/artifacts -H "Authorization: Bearer $TEDDY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>Hola</h1>","title":"Saludo","folder":"Demos","expires_in":"7d"}'

Funciones

Una función es un módulo JavaScript que Teddy ejecuta en un sandbox (isolate V8, el mismo motor que Cloudflare Workers) y expone en /f/<id>, con cualquier método y subruta.

export default {
  async fetch(request, env) {
    const { importe = 0 } = await request.json().catch(() => ({}));
    return Response.json({ importe, iva: +(importe * 0.21).toFixed(2) });
  }
};
curl -X POST https://teddyagent.app/api/functions -H "Authorization: Bearer $TEDDY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"IVA","code":"export default { async fetch(r) { return Response.json({ ok: true }) } }"}'

Acceso a tus APIs

Cada función tiene un modo de acceso (access):

curl -X POST https://teddyagent.app/api/functions/<id>/keys -H "Authorization: Bearer $TEDDY_KEY" \
  -H "Content-Type: application/json" -d '{"name":"Cliente Acme","rate_per_min":60,"expires_in":"30d"}'

curl https://teddyagent.app/f/<id> -H "Authorization: Bearer tfk_…"   # o X-API-Key, o ?api_key=

La función nunca ve la clave: recibe x-teddy-key-name con su nombre (codificado con encodeURIComponent) y x-teddy-key-id; cualquier cabecera x-teddy-* que mande el cliente se descarta. Las llamadas con clave tfk_ reciben X-RateLimit-Limit y X-RateLimit-Remaining. Los intentos rechazados (401/403/404/429) se registran con su motivo, se ven en el panel y en /rejections, y no gastan cuota ni límite.

Funciones programadas (cron)

Una función puede ejecutarse sola. Defines una expresión cron de 5 campos en UTC (minuto hora día-del-mes mes día-de-la-semana; admite listas, rangos, pasos, nombres como mon-fri y alias @hourly, @daily, @weekly) y Teddy llama a la función con un POST a la ruta que elijas, con el cuerpo que configures. Llegan tres cabeceras que nadie de fuera puede falsificar: x-teddy-cron: 1, x-teddy-schedule-id y x-teddy-scheduled-for (la hora prevista).

curl -X POST https://teddyagent.app/api/functions/<id>/schedules \
  -H "Authorization: Bearer $TEDDY_KEY" -H "Content-Type: application/json" \
  -d '{"cron":"0 9 * * mon-fri","path":"/informe","body":{"dias":7},"name":"Informe diario"}'

Cuenta contra las invocaciones diarias del plan y respeta el tiempo máximo por invocación. Free: 2 programaciones, como mucho una por hora; Pro: 50, cada 5 minutos. Si Teddy está parado en el momento previsto, la ejecución se hace una vez al volver (no todas las perdidas). Cada ejecución queda en un registro de 14 días (GET /api/functions/<id>/schedules/<sid>/runs, o en el panel); tras 10 fallos seguidos la programación se desactiva sola y lo dice, y al reactivarla se pone a cero. Si la función está pausada o la cuenta suspendida, la ejecución se omite y se anota.

Secretos

Para claves de APIs externas. Nunca las pongas en el código: guárdalas como secreto y léelas con env.NOMBRE.

curl -X PUT https://teddyagent.app/api/functions/<id>/secrets/OPENAI_API_KEY -H "Authorization: Bearer $TEDDY_KEY" \
  -H "Content-Type: application/json" -d '{"value":"sk-…"}'
export default {
  async fetch(request, env) {
    const r = await fetch("https://api.openai.com/v1/models", { headers: { Authorization: "Bearer " + env.OPENAI_API_KEY } });
    return new Response(r.body, r);
  }
};   // con allow_net: ["api.openai.com"]

Nombres en MAYÚSCULAS con números y _ (KV, FN_* y TEDDY_* están reservados); valores de 4 B a 4 KB. Se guardan cifrados (AES-256-GCM). El valor no se puede volver a leer y, si aparece en un log o en un error, sale como ••••.

Memoria (KV)

Cada función publicada tiene su propio almacén clave-valor, persistente entre llamadas:

export default {
  async fetch(request, env) {
    const visitas = Number(await env.KV.get("visitas") ?? 0) + 1;
    await env.KV.put("visitas", String(visitas));
    await env.KV.put("ultima", JSON.stringify({ t: Date.now() }), { expirationTtl: 86400 });
    const { keys } = await env.KV.list({ prefix: "u:" });
    return Response.json({ visitas, ultima: await env.KV.get("ultima", "json"), usuarios: keys.length });
  }
};

Los valores son texto (usa JSON.stringify). Desde el panel o la API ves, lees y borras claves. run_code no tiene KV.

Datos (colecciones)

Una colección es una lista de registros JSON con su propia API en https://teddyagent.app/d/<id>: guardas pedidos, respuestas de un formulario o el estado de una app sin montar una base de datos. La lectura y la escritura tienen cada una su acceso: public (cualquiera, con límite por IP), key (clave tfk_… o de gestión) u owner (solo tú). Las páginas que publicas pueden llamarla con fetch directamente (CORS abierto).

# crear un registro y leerlos con filtros
curl -X POST https://teddyagent.app/d/ID -H "Content-Type: application/json" -d '{"nombre":"Ana","mesas":2}'
curl "https://teddyagent.app/d/ID?mesas__gte=2&_order=-mesas&_limit=20"
# cambiar un campo (null lo borra) solo si nadie lo cambió antes
curl -X PATCH https://teddyagent.app/d/ID/REGISTRO -H "If-Match: 1" -d '{"mesas":3}'

Filtros: campo=valor, campo__gt, __gte, __lt, __lte, __ne; orden _order=-campo; _limit (máx. 100), _offset, _count=1. Cada registro tiene id, version y fechas; If-Match: <versión> evita pisar cambios ajenos (409 version_conflict). Un esquema opcional (subconjunto de JSON Schema) valida lo que se escribe.

Desde una función: env.DB.collection("pedidos") con find, get, insert, put, patch y delete; solo ve las colecciones de su dueño. run_code no tiene DB. Desde el panel (Datos) ves los registros, cambias accesos y exportas a CSV o JSON; la API de gestión (/api/collections) también importa hasta 1 000 registros por llamada.

Tiempo real

Un canal (https://teddyagent.app/c/<id>) reparte mensajes JSON al instante a todo el que lo escucha: chat, marcadores, avisos, paneles que se actualizan solos. Publicar y escuchar tienen cada uno su acceso (public, key u owner), como en las colecciones. Se crean en el panel (Canales), con la API o con tu agente.

// en una página publicada: escuchar (SSE; se reconecta solo y no pierde mensajes)
const es = new EventSource("https://teddyagent.app/c/ID");
es.onmessage = (e) => console.log(JSON.parse(e.data));
es.addEventListener("marcador", (e) => console.log("gol", JSON.parse(e.data)));

// publicar
fetch("https://teddyagent.app/c/ID?event=marcador", { method: "POST", body: JSON.stringify({ local: 2, visitante: 1 }) });

// WebSocket: lo que llega es {id, event, data}; lo que escribas (JSON) se publica
const ws = new WebSocket("wss://TU-DOMINIO/c/ID");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onopen = () => ws.send(JSON.stringify({ hola: "mundo" }));

Los canales con clave (tfk_…, de toda la cuenta) la reciben en Authorization: Bearer o X-API-Key; los navegadores no pueden poner cabeceras en EventSource ni WebSocket, así que ahí va en ?api_key=. Nunca se usan cookies. Al reconectar, Last-Event-ID (o ?after=) recupera lo perdido entre los últimos 100 mensajes del canal: los mensajes no se guardan en disco, si necesitas histórico guárdalos en una colección.

GET /d/<id>/_events emite en directo los cambios de una colección (created, updated, deleted) con la misma regla de acceso que su lectura: una página puede mostrar los datos y mantenerlos al día sin consultar. Desde una función, await env.CHANNELS.publish("nombre", datos, "evento") avisa a los oyentes del canal de tu cuenta (no existe en run_code).

Límite conocido: el bus de mensajes vive en la memoria de un único proceso de Teddy; con varias instancias haría falta un bus compartido (Postgres LISTEN/NOTIFY). Los latidos (: ping cada 25 s) mantienen viva la conexión tras proxies.

Login de tus clientes

Tu página puede pedir a sus propios visitantes que inicien sesión (con Google) sin que montes nada: Teddy hace el login en su dominio, el cliente confirma que quiere entrar, y tu página y tus funciones saben quién es. No son cuentas de Teddy: son tus clientes, solo tuyos (los ves en Clientes).

<script src="https://teddyagent.app/sdk/teddy.js"></script>
<script>
  await Teddy.ready;                          // recupera la sesión si ya había una
  if (!Teddy.user) await Teddy.login();       // abre el popup de Teddy
  console.log(Teddy.user.email, Teddy.user.name);
  Teddy.onChange((u) => console.log("ahora:", u));
  // Teddy.fetch añade la sesión; tu función recibe la identidad en cabeceras de confianza
  const r = await Teddy.fetch("https://teddyagent.app/f/ID_FUNCION/mis-pedidos");
  await Teddy.logout();
</script>

La función (con access: "user") recibe x-teddy-user-id (eu_…, estable por cliente y dueño), x-teddy-user-email, x-teddy-user-name (codificados con encodeURIComponent) y x-teddy-user-provider. Son de confianza: Teddy descarta cualquier cabecera x-teddy-* que mande el cliente y la función nunca ve el token. Sin sesión válida responde 401 (login_required o invalid_session). Guardar los datos de cada cliente por separado (p. ej. un campo owner_email en tus registros y filtrar por él dentro de la función con env.DB) lo haces tú: así la página nunca lee datos de otros.

export default { async fetch(req, env) {
  const email = decodeURIComponent(req.headers.get("x-teddy-user-email"));
  const mine = await env.DB.query("presupuestos", { filters: { cliente: email } });
  return Response.json(mine.items.map((r) => r.data));
} }
Límites y detalles. El login solo funciona en la página normal (/a/<id>), no en el modo ?bare=1 (Teddy.login() falla con no_shell). La sesión dura 14 días, vive en el navegador de ese cliente y se puede cerrar o borrar desde el panel; deja de valer si pausas o borras la página donde se inició. Los clientes que llevan 90 días sin entrar y sin sesión se eliminan solos (liberan cupo). Teddy.fetch solo envía la sesión a las funciones de este Teddy (/f/…). Máximo de clientes por plan: Free 100, Pro 100.000. Tus páginas siguen en un entorno aislado: no puedes insertar el botón de Google dentro de ella (su origen es opaco); usa Teddy.login(). Puedes insertar contenido de terceros con <iframe>, que hereda ese aislamiento y no recibe Referer: los que lo exigen (el reproductor de YouTube) muestran error; los que no (muchos mapas y widgets) funcionan. Proveedores: Google (requiere GOOGLE_CLIENT_ID y GOOGLE_CLIENT_SECRET en el servidor y registrar https://teddyagent.app/eu/google/callback como URI de redirección); la «cuenta de prueba» es solo para demostración y nunca está activa en producción.

MCP para agentes

Servidor MCP remoto (Streamable HTTP) en https://teddyagent.app/mcp. Dos formas de autenticarse:

Todas las herramientas declaran título y si solo leen o si cambian/borran cosas (readOnlyHint / destructiveHint). Herramientas disponibles:

publish_artifactPublica HTML y devuelve la URL. Acepta título, carpeta (por nombre), estado y caducidad.
update_artifactSustituye el HTML o cambia título, carpeta, estado (live/paused) o caducidad.
delete_artifactBorra un artifact.
get_artifactMetadatos de un artifact.
list_artifactsLista tus artifacts; filtra por carpeta y estado.
list_artifact_versionsHistorial de versiones de un artifact.
restore_artifact_versionVuelve a publicar una versión anterior (como versión nueva).
create_share_linkCrea un enlace secreto para compartir (también si es privado), con etiqueta y caducidad opcionales.
list_share_linksEnlaces secretos de un artifact con sus visitas.
revoke_share_linkRevoca un enlace secreto (deja de abrir el artifact).
list_foldersTus carpetas y cuántos artifacts tiene cada una.
list_templatesPlantillas para estudiantes, programadores y equipos: apuntes de clase, test, tarjetas, plan de estudio, guía de proyecto y acta.
get_templateEl HTML de una plantilla para conservar su estructura y cambiar solo el contenido.
run_codeEjecuta código una vez en el sandbox, sin publicar.
publish_functionPublica una función como endpoint /f/<id>.
update_functionNueva versión, nombre, acceso, red o estado.
list_functionsLista tus funciones.
function_logsÚltimas invocaciones con estado, tiempos, console.log y rechazos.
delete_functionBorra una función.
create_function_keyCrea una clave de consumo tfk_ con límites y caducidad.
list_function_keysLista las claves de consumo y su uso.
revoke_function_keyRevoca una clave al instante.
set_function_secretGuarda un secreto cifrado (env.NOMBRE).
list_function_secretsNombres de los secretos (nunca valores).
delete_function_secretBorra un secreto.
create_schedulePrograma una función con un cron (UTC).
list_schedulesLista tus funciones programadas y su último resultado.
update_scheduleCambia cron/ruta/cuerpo o activa y desactiva una programación.
delete_scheduleBorra una programación.
schedule_runsRegistro de ejecuciones de una programación.
get_usageCuánto llevas usado de tu plan (artefactos, espacio, funciones) y cuánto te queda.
whoamiTu email, plan, límites y uso.
list_collectionsLista tus colecciones de datos con su acceso y tamaño.
create_collectionCrea una colección de datos con su API /d/<id> (acceso y esquema opcional).
update_collectionCambia nombre, acceso o esquema de una colección.
delete_collectionBorra una colección y todos sus registros.
list_channelsLista tus canales de tiempo real con su acceso y oyentes.
create_channelCrea un canal de tiempo real (/c/<id>) para mensajes en directo por SSE o WebSocket.
update_channelCambia nombre o acceso de un canal (cierra las conexiones abiertas).
delete_channelBorra un canal y corta a sus oyentes.
publish_messagePublica un mensaje JSON en un canal: llega al instante a sus oyentes.
read_messagesLee los últimos mensajes de un canal (anillo en memoria).
query_recordsLee registros con filtros, orden y paginación.
write_recordCrea, reemplaza o fusiona un registro (con control de versión).
delete_recordBorra un registro.
list_end_usersLista los clientes que han iniciado sesión en tus páginas.
delete_end_userBorra a un cliente y cierra sus sesiones.

Planes y límites

FreePro
Artifacts501000
Tamaño por artifact2 MB10 MB
Publicaciones por hora60600
Claves de agente520
Funciones10200
Código por función256 KB1 MB
Tiempo por invocación10 s30 s
Invocaciones al día10.0001.000.000
Llamadas/min por clave (por defecto)6006000
Llamadas/min por IP (APIs públicas)120600
Ejecuciones run_code por hora1201200
Secretos por función20100
KV: claves / espacio1000 / 10 MB100.000 / 1 GB
KV: tamaño por valor64 KB1 MB
Colecciones550
Registros por cuenta5000500.000
Tamaño por registro16 KB256 KB
Datos: espacio total20 MB5 GB
Canales5100
Mensaje de canal16 KB64 KB
Mensajes/s por canal20200
Oyentes por canal1002000
Conexiones abiertas por cuenta2005000

Superar un límite devuelve 429 (o 403 si es de cupo), con un mensaje claro y, cuando aplica, Retry-After.

Pasar a Pro: en el panel, Plan → «Pasar a Pro» (pago mensual; cancelas cuando quieras y mantienes Pro hasta el final del mes pagado). Al volver a Free no se borra nada: lo que supere los límites sigue ahí, pero no podrás crear más hasta estar por debajo. Comprar o cancelar solo se hace desde el panel; con una clave puedes consultar el estado en GET /api/billing.

API REST

Base https://teddyagent.app. Autenticación Authorization: Bearer tdy_…. Especificación completa en /openapi.json (OpenAPI 3.1): impórtala en Postman, Insomnia o genera un cliente.

Cuenta

GET/api/healthEstado del servicio sin clave
GET/api/plansPlanes y límites sin clave
GET/api/meQuién soy: email, plan, límites y uso
GET/api/billingPlan, suscripción e historial de pagos
GET/api/me/exportDescargar todos tus datos (ZIP)
GET/api/me/onboardingEstado de «Primeros pasos»
GET/api/usageUso del plan: artefactos, espacio y funciones frente a los límites (level ok, warn al 80 %, full al 100 %)

Artifacts

GET/api/artifactsListar mis artifacts
POST/api/artifactsPublicar un artifact
GET/api/artifacts/{id}Ver un artifact (metadatos) sin clave
PUT/api/artifacts/{id}Sustituir el HTML
PATCH/api/artifacts/{id}Cambiar título, estado (live/paused), carpeta, caducidad, visibilidad (public/private) o contraseña
DELETE/api/artifacts/{id}Borrar
GET/api/artifacts/{id}/filesArchivos del artifact además del HTML (versión actual)
GET/api/artifacts/{id}/versionsHistorial de versiones (cada HTML publicado)
GET/api/artifacts/{id}/versions/{n}Contenido de una versión (JSON con html, o ?format=html para descargarlo)
POST/api/artifacts/{id}/versions/{n}/restoreRestaurar una versión (se publica como versión nueva)
GET/api/artifacts/{id}/diffDiferencias línea a línea entre dos versiones
POST/api/artifacts/{id}/reportReportar un artifact (abuso, phishing…) sin clave

Carpetas

GET/api/foldersListar carpetas
POST/api/foldersCrear carpeta
PATCH/api/folders/{id}Renombrar
DELETE/api/folders/{id}Borrar (los artifacts pasan a «sin carpeta»)
POST/api/folders/{id}/indexCrear (o actualizar) la página «Índice · carpeta» con enlaces a todo lo de la carpeta

Claves de agente

GET/api/keysListar mis claves tdy_
POST/api/keysCrear una clave tdy_ (solo se muestra una vez)
DELETE/api/keys/{id}Revocar

Funciones

GET/api/functionsListar mis funciones
POST/api/functionsPublicar una función como endpoint /f/{id}
GET/api/functions/{id}Ver una función (con código)
PUT/api/functions/{id}Publicar nueva versión del código
PATCH/api/functions/{id}Cambiar nombre, estado (live/paused), acceso, red o carpeta
DELETE/api/functions/{id}Borrar (con sus claves, secretos y memoria)
GET/api/functions/{id}/logsÚltimas invocaciones (incluye rechazadas, con motivo)

Acceso a funciones

GET/api/functions/{id}/usageUso de los últimos 7 días por clave
GET/api/functions/{id}/rejectionsIntentos rechazados (7 días) por motivo
GET/api/functions/{id}/keysClaves de consumo válidas para esta función
POST/api/functions/{id}/keysCrear clave de consumo tfk_ solo para esta función
GET/api/function-keysTodas mis claves de consumo
POST/api/function-keysCrear clave de consumo (de una función o de todas)
PATCH/api/function-keys/{id}Cambiar nombre y límites
DELETE/api/function-keys/{id}Revocar al instante

Secretos

GET/api/functions/{id}/secretsListar nombres de secretos (nunca valores)
POST/api/functions/{id}/secretsCrear o reemplazar un secreto
PUT/api/functions/{id}/secrets/{name}Crear o reemplazar un secreto por nombre
DELETE/api/functions/{id}/secrets/{name}Borrar un secreto

Programaciones

GET/api/schedulesTodas mis funciones programadas
GET/api/functions/{id}/schedulesProgramaciones de esta función
POST/api/functions/{id}/schedulesProgramar la función (cron de 5 campos, en UTC)
GET/api/functions/{id}/schedules/{sid}Ver una programación
PATCH/api/functions/{id}/schedules/{sid}Cambiar cron, ruta, cuerpo, nombre o activarla/desactivarla
DELETE/api/functions/{id}/schedules/{sid}Borrar la programación (y su registro)
GET/api/functions/{id}/schedules/{sid}/runsRegistro de ejecuciones (14 días)
POST/api/functions/{id}/schedules/{sid}/runEjecutarla ahora (máx. 20 por hora)

Memoria (KV)

GET/api/functions/{id}/kvListar claves del KV de la función
DELETE/api/functions/{id}/kvVaciar el KV
GET/api/functions/{id}/kv/{key}Leer una clave
DELETE/api/functions/{id}/kv/{key}Borrar una clave

Ejecución

POST/api/runEjecutar código una vez, sin publicar

Datos (colecciones)

GET/api/collectionsListar tus colecciones
POST/api/collectionsCrear una colección de datos
GET/api/data-usageUso de datos: colecciones, registros y bytes frente al plan
GET/api/collections/{id}Ver una colección
PATCH/api/collections/{id}Cambiar nombre, acceso o esquema
DELETE/api/collections/{id}Borrar la colección y todos sus registros
GET/api/collections/{id}/recordsRegistros de la colección (vista del dueño; mismos filtros que la API pública)
POST/api/collections/{id}/recordsCrear un registro (el cuerpo es el propio objeto JSON)
DELETE/api/collections/{id}/recordsVaciar la colección
GET/api/collections/{id}/records/{rid}Leer un registro
PUT/api/collections/{id}/records/{rid}Reemplazar (o crear con ese id) un registro; cabecera `If-Match` con la versión
PATCH/api/collections/{id}/records/{rid}Fusionar campos en un registro (null elimina un campo)
DELETE/api/collections/{id}/records/{rid}Borrar un registro
POST/api/collections/{id}/importImportar hasta 1000 registros de una vez (todo o nada)
GET/api/collections/{id}/exportExportar todos los registros (`format`: json, ndjson o csv)

Canales (tiempo real)

GET/api/channelsListar tus canales
POST/api/channelsCrear un canal de tiempo real
GET/api/channel-usageUso de canales y conexiones abiertas frente al plan
GET/api/channels/{id}Ver un canal
PATCH/api/channels/{id}Cambiar nombre o acceso (cierra las conexiones abiertas)
DELETE/api/channels/{id}Borrar el canal y cortar a sus oyentes
POST/api/channels/{id}/publishPublicar un mensaje como dueño
GET/api/channels/{id}/messagesÚltimos mensajes (anillo en memoria; `after` = último id visto)

Canales: API pública

GET/c/{id}Escuchar: SSE (`Accept: text/event-stream`), WebSocket (`Upgrade: websocket`) o, sin ellos, los últimos mensajes en JSON sin clave
POST/c/{id}Publicar un mensaje (cuerpo: cualquier JSON; `?event=nombre`) sin clave

Usuarios finales

GET/api/end-usersListar los clientes que han iniciado sesión en tus apps
DELETE/api/end-users/{id}Borrar a un cliente y cerrar sus sesiones

Usuarios finales: API de sesión

GET/eu/api/meQuién es el cliente de esta sesión (`Authorization: Bearer tus_…`) sin clave
POST/eu/api/logoutCerrar la sesión del cliente sin clave

Datos: API pública

GET/d/{id}Listar registros con filtros sin clave
POST/d/{id}Crear un registro sin clave
GET/d/{id}/{rid}Leer un registro sin clave
PUT/d/{id}/{rid}Reemplazar o crear con id elegido (`If-Match`: versión) sin clave
PATCH/d/{id}/{rid}Fusionar campos (null elimina; `If-Match`: versión) sin clave
DELETE/d/{id}/{rid}Borrar (`If-Match`: versión) sin clave
GET/d/{id}/_eventsCambios de la colección en directo (SSE): `created`, `updated`, `deleted` sin clave

Errores

Siempre JSON con error (texto legible) y a veces code (estable, para programas): unauthorized, invalid_api_key, quota_exceeded, rate_limited, too_large, moderation_rejected, no_default_export…

{ "error": "Tu plan permite 20 secretos por función", "code": "quota_exceeded" }