Saltar al contenido principal

Streams y feeds

Un stream es una dirección a la que sus propios sistemas envían datos. Lleva una plantilla y, cuando su documento no encaja ya en esa plantilla, un mapa de campos que indica dónde está en su documento cada entrada de la plantilla. Los canales se suscriben al stream y heredan ambos, de modo que hay exactamente un lugar donde se escribe la asignación y exactamente un lugar donde se comprueba.

Los feeds son la otra mitad: los datos que incluye Reelwire, que reciben los canales de un espacio de trabajo en lugar de crearlos el espacio de trabajo. Estas rutas configuran ambos. Enviar a un stream es Enviar eventos. En el panel, los streams están en Feeds propios y los feeds en Feeds predefinidos.

MétodoRutaPermiso
GET/v1/streamsstreams:read
POST/v1/streamsstreams:write
POST/v1/streams/{id}/updatestreams:write
POST/v1/streams/{id}/deletestreams:write
POST/v1/streams/{id}/suggest-mapstreams:write
POST/v1/streams/{id}/testpersona con sesión iniciada
GET/v1/feedsfeeds:read
PUT/v1/feedsfeeds:write
GET/v1/feeds/{feedId}/referencefeeds:read
GET/v1/feeds/{feedId}/reference/{ratio}feeds:read

Streams y feeds son dos áreas de permisos, Feeds propios (streams) y Feeds predefinidos (feeds), porque son dos cosas distintas: un stream es su propio conducto, un feed es algo que incluye Reelwire. /v1/streams y /v1/stream también son rutas distintas en áreas distintas. La del plural configura streams y necesita streams; la del singular recibe eventos y necesita ingest:write.

Crear un stream​

curl http://localhost:4000/v1/streams \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Docs examples stream",
"description": "Created while writing the reference",
"templateId": "tpl_dd7e6b264fb8",
"fieldMap": {
"/clipData/label": { "from": "/article/title", "as": "string", "maxLength": 70 },
"/clipData/value": { "from": "/article/figure", "as": "number" },
"/clipData/source": { "const": "Orchid Invest" }
}
}'
{
"stream": {
"id": "st_d84d89e39bfc",
"name": "Docs examples stream",
"description": "Created while writing the reference",
"enabled": true,
"templateId": "tpl_dd7e6b264fb8",
"fieldMap": {
"/clipData/label": { "as": "string", "from": "/article/title", "maxLength": 70 },
"/clipData/value": { "as": "number", "from": "/article/figure" },
"/clipData/source": { "const": "Orchid Invest" }
},
"subscribers": 0,
"listeners": []
}
}

201. El cuerpo:

CampoObligatorioQué es
idnost_ y doce caracteres hexadecimales, cuando quiera elegirlo. Si lo omite, Reelwire genera uno. Un id ya ocupado en cualquier espacio de trabajo es un 409.
namesíDe 1 a 120 caracteres.
descriptionnoHasta 500 caracteres.
enablednoPor defecto true.
templateIdsíLa plantilla con la que se renderiza cada evento de este stream, una que se ofrezca a este espacio de trabajo. Cualquier otra es un 404.
fieldMapnoPor defecto {}, que es traspaso directo.

Un templateVariant o templateVersion enviado aquí se descarta: qué corte y qué compilación renderizan un clip es una elección de cada canal que se suscribe.

La plantilla tiene que tener una compilación con la que renderizar, o el stream se rechaza con un 404 que lo dice. Un espacio de trabajo tiene hasta 50 streams, salvo que Reelwire lo haya fijado de otra manera; por encima, un 409 nombra el número. Un espacio de trabajo cuya suscripción se canceló no puede crear streams nuevos (un 409) hasta que vuelva a suscribirse, en cualquier plan, Free incluido.

El mapa de campos​

Un mapa es plano: cada clave es un JSON Pointer al documento de entrada de la plantilla, y cada valor indica de dónde sale esa entrada en su documento.

El título y el resumen de un elemento RSS enviados a un stream, cuyo mapa de campos los coloca en el titular y el subtítulo de la plantilla, con una constante para la línea de la fuente

ClaveQué hace
fromUn JSON Pointer a los data enviados.
constUn valor fijo, para una entrada para la que sus datos no tienen equivalente.
fallbackSe usa cuando from no se resuelve en nada. Ausente no es lo mismo que vacío.
asConversión antes de validar: string, number, integer, boolean o iso-date.
maxLengthRecortar el texto demasiado largo al límite de la plantilla en lugar de dejar que falle la validación.

Dé a cada entrada un from o un const. Cuando from no encuentra nada, se usa fallback y luego const si la entrada tiene ambos; sin fallback ni const, el evento es unmapped y sus mappingErrors nombran el campo (consulte los estados). Las claves deben empezar por /, y su primer paso debe ser una de las entradas propias de la plantilla (/clipData/... o /metaData/... en cada plantilla del catálogo): un mapa dirigido a cualquier otro sitio es un 400 que lista lo que acepta la plantilla, cuando el stream se crea o se modifica.

Un mapa vacío significa traspaso directo, no «descartarlo todo», y ahora es el caso corriente: una plantilla publica el formato que acepta en /v1/formats/template/{templateId}, de modo que un productor escrito contra eso ya emite el documento correcto y no tiene nada que asignar.

Puede ver ejecutarse un mapa sin enviar nada, en /v1/stream/validate. Entra el documento en bruto y document vuelve tal como lo verá la plantilla:

curl http://localhost:4000/v1/stream/validate \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"streamId": "st_d84d89e39bfc",
"eventId": "01M2SQMKK3G1NVFXV31DCBVB95",
"occurredAt": "2026-09-18T08:00:00Z",
"data": {
"article": { "title": "Assets under management at Orchid Invest", "figure": "12.4", "body": "..." },
"publishedBy": "newsroom"
}
}'
{
"requestId": "01M2SQYACSHKW9FHFD8RYAK1WT",
"streamId": "st_d84d89e39bfc",
"templateId": "tpl_dd7e6b264fb8",
"templateVersion": "1.0.0",
"valid": true,
"document": {
"clipData": {
"label": "Assets under management at Orchid Invest",
"value": 12.4,
"source": "Orchid Invest"
}
},
"warnings": [{ "path": "/clipData/value", "message": "converted to number" }],
"errors": [],
"wouldMatch": []
}

wouldMatch está vacío porque ningún canal se suscribe aún a este stream. El documento es válido igualmente. Cada conversión que cambió un valor, cada fallback usado y cada texto recortado a maxLength es una advertencia; un puntero que no encuentra nada, sin fallback ni const, o un valor que no se puede convertir, es un error.

Cada canal se comprueba contra la variante que renderiza. Las variantes de una plantilla pueden admitir contenido distinto: en un package, cada variante es otra publicación con sus propios campos, y un campo que exige una variante puede no tenerlo otra. Así que el documento se comprueba una vez por cada canal que recibe el stream, contra la variante y versión de ese canal, y wouldMatch lista solo los canales que producirían una publicación. Un canal cuya variante rechaza el documento se deja fuera y se nombra en warnings («Not posted on ...»), mientras los demás siguen publicando; cuando la variante de ningún canal lo admite, valid es false y errors indican por qué, nombrando los canales y la variante donde difieren. Sin ningún canal que lo reciba, el documento se comprueba contra la primera variante de la plantilla. Un evento real en /v1/stream se juzga igual: aceptado para los canales a los que encaja, con los demás nombrados en warnings, y unmapped, con mappingErrors, solo cuando no encaja en ninguno.

Dejar que Reelwire sugiera un mapa​

POST /v1/streams/{id}/suggest-map compara los nombres de campo propios con las entradas de la plantilla y propone un mapa. Necesita una carga real de la que partir, y lo dice claramente cuando no hay ninguna, con un 409:

curl http://localhost:4000/v1/streams/st_d84d89e39bfc/suggest-map \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
{
"error": {
"type": "https://reelwire.io/errors/no_sample",
"code": "no_sample",
"message": "This stream has not received anything yet",
"requestId": "01M2SQYKXH7B2JW3PWQ24YHV3Q",
"details": [
{
"path": "",
"message": "Send one event from your script first. The suggestion is built from your own field names, so there is nothing to match against until then."
}
]
}
}

Envíe un evento y vuelva a preguntar. Un stream guarda para esto la carga más reciente que recibió. Cuando tiene una, la respuesta es suggestion (un mapa para revisar, que nunca se aplica por sí solo), templateFields (cada entrada que tiene la plantilla) y sample (la carga con la que se comparó). Envíe templateId, y templateVersion si lo desea, para ver un mapa para otra plantilla antes de cambiar.

Listar streams​

curl http://localhost:4000/v1/streams \
-H "Authorization: Bearer rw_your_key_here"
{
"route": "http://localhost:4000/v1/stream",
"streams": [
{
"id": "st_30472168d941",
"name": "Enterprise 2 Stream 1",
"enabled": true,
"templateId": "tpl_dd7e6b264fb8",
"fieldMap": {},
"lastReceivedAt": "2026-09-18T07:45:50.088Z",
"subscribers": 2,
"listeners": [
{ "channelId": "01M2985NX4V0WA1GV9MC63DEPJ", "channelName": "Channel 4 de" },
{ "channelId": "01M2985NXJRXVE5HKCCGKASP8P", "channelName": "Channel 10 en" }
]
}
]
}

subscribers es cuántos canales consumen el stream. Cero significa que no se renderiza nada, envíe lo que envíe. Cada stream lleva además lastSample, la carga más reciente que recibió, cuando ya tiene una.

Cambiar o eliminar un stream​

POST /v1/streams/{id}/update admite los mismos campos que crear, todos opcionales, excepto id: el id no se puede editar a propósito, porque su script lo nombra en cada solicitud y cambiarlo parecería desde su lado que Reelwire devuelve 404 sin motivo. Cambiar templateId o fieldMap se comprueba contra la compilación más reciente de la plantilla antes de guardarse, y contra la última carga del stream cuando la tiene, de modo que un mapa que se guarda es un mapa que se renderiza. Responde { "ok": true }. Una plantilla nueva devuelve a cada canal suscrito a la primera variante de la plantilla y a su compilación más reciente, porque lo que habían elegido pertenecía a la plantilla antigua.

POST /v1/streams/{id}/delete elimina el stream y responde { "ok": true, "stopped": 2 }: desde entonces se rechazan los eventos enviados a él, y los canales que lo recibían dejan de hacerlo, que es lo que cuenta stopped. Lo que ya había recibido se sigue renderizando y publicando. El historial del stream se conserva, bajo su nombre con « (removed)» al final, así que revise listeners antes de llamarlo.

Probar un stream desde el panel​

POST /v1/streams/{id}/test es la ruta que hay detrás de la Herramienta de prueba de Feeds propios, y es solo de una persona con sesión iniciada: toda clave de API es rechazada allí, tenga lo que tenga, y la persona necesita Operaciones, como para crear un stream. Admite los datos del evento y construye el resto del envoltorio como lo haría su productor: el stream de la ruta, un eventId nuevo y ahora como occurredAt.

CampoObligatorioQué es
datasíUn objeto: lo que enviaría un productor como data.
checknotrue ejecuta la prueba en seco y no guarda nada. Si se omite, el evento se envía de verdad.

Con check, la respuesta es { "check": ... }, con exactamente lo que responde POST /v1/stream/validate. Sin él, la respuesta es un 202 { "sent": ... }, con lo que responde POST /v1/stream, y cada canal que recibe el stream renderiza y entrega una publicación de él como cualquier otra. Un stream en pausa o desconocido se rechaza como allí.

Es de una persona porque envía eventos reales: una clave solo lo hace con Custom feeds - Send raw data (ingest:write), en POST /v1/stream, y una persona que prueba un stream no debería tener que crear primero una clave. Consulte Enviar eventos.

Feeds​

Los feeds predefinidos llevan datos en vivo: el servidor de feeds propio de Reelwire los obtiene de fuentes públicas, construye el contenido que admite la plantilla de cada feed y envía cada evento a cada espacio de trabajo con un canal que lo recibe. Nada de ello necesita una clave ni un productor suyo.

GET /v1/feeds lista cada feed incluido que se ofrece a la cuenta, agrupado por fuente, con la plantilla con la que se renderiza cada uno, los canales que lo reciben (listeners, contados en subscribers), cuándo se espera que publique, y languages, los idiomas en que viene su contenido, con en siempre entre ellos: un canal en otro idioma recibe su contenido en inglés. Es lo que muestra la pantalla Feeds predefinidos.

Lo que se ofrece a un espacio de trabajo sigue los sectores que eligió su propietario (consulte Sus sectores): un feed calificado como esencial o posible para uno de ellos. Nada más desbloquea uno. Eso decide a qué puede suscribirse un canal, y nada después: no hay comprobación de sector sobre los eventos en sí, de modo que un feed que ya no se ofrece a la cuenta sigue en la lista, y sigue publicando, mientras un canal lo reciba.

curl http://localhost:4000/v1/feeds \
-H "Authorization: Bearer rw_your_key_here"
{
"sources": [
{
"slug": "crypto-spot-and-dominance",
"title": "Crypto spot and dominance",
"category": "Market and financial data",
"description": "Prices, market values and 24-hour moves of the largest coins, and Bitcoin's share of the whole crypto market.",
"cadence": "Daily, and price milestones as they happen",
"period": "Daily",
"implementation": "Feed server: crypto market data",
"milestone": 1,
"entitled": false,
"live": true,
"formats": [
{
"id": "fd_c16f5268064a",
"slug": "crypto-board",
"name": "Crypto board",
"content": null,
"title": "Crypto board",
"example": "The eight largest coins with their 24-hour moves",
"description": "The eight largest coins by market value, excluding stablecoins, with their price and 24-hour move. Every morning.",
"group": "Crypto spot and dominance",
"contentStyle": null,
"milestone": 1,
"templateId": "tpl_06dc2554e5ac",
"templateName": "Number grid",
"templateVariant": "Data terminal",
"maxAgeSeconds": null,
"live": true,
"entitled": false,
"offered": true,
"enabled": true,
"subscribers": 1,
"listeners": [{ "channelId": "01M1VFRWE7EWSY5D7QDQ0KNK1S", "channelName": "Channel 1 en" }],
"schedule": { "every": "daily", "time": "08:00" },
"scheduleLabel": "Daily 08:00",
"scheduleNote": null,
"sentLast30Days": 30,
"multilingual": false,
"languages": ["en"],
"type": "live",
"plans": ["FREE", "INDIVIDUAL", "SMALL_BUSINESS", "ENTERPRISE"],
"usable": true
}
]
}
]
}

id es el id del feed, fd_ y doce caracteres hexadecimales. Dirija un feed por su id dondequiera que se pida uno: los slugs son palabras y las palabras se editan, y un productor fijado a un título se rompe el día en que alguien corrige sus mayúsculas. Un feed que añadió Reelwire por sí mismo aparece bajo el slug de fuente custom, con su propio id como slug de formato.

offered es el campo que hay que leer. entitled es un resto de cuando los feeds se concedían por plan y ya no decide nada. enabled es el interruptor propio del espacio de trabajo, abajo. templateId, templateName y templateVariant son la plantilla y el estilo con los que se renderiza el feed, de los que parte un canal que se suscribe a él. schedule y scheduleLabel indican cuándo se espera que publique el feed, como lo muestra el Calendario de publicaciones, y scheduleNote lo dice con palabras donde no hay regla. maxAgeSeconds es null aquí; un evento de más de un día se conserva pero nunca se publica (el maxAgeSeconds del catálogo, consulte Plantillas y formatos). multilingual y languages indican en qué idiomas viene el feed. true admite cualquier language que elija la suscripción de un canal y traduce el feed a ese idioma con IA, lo que puede cambiar la redacción de lo que le importa a un regulador; languages es entonces ["en"], el idioma que escribe su propio código. false viene en los idiomas que nombra languages, el predeterminado primero, cada uno escrito por el código propio del feed y nunca por IA (consulte Suscripciones). type indica cómo se hace su contenido: evergreen (escrito una vez con IA, revisado por una persona, publicado según un calendario, no son noticias), live (datos nuevos de fuentes públicas en cada ejecución, su redacción quizá pulida y traducida por IA) u official (directamente de la autoridad o proveedor con licencia que publica los datos, o calculado por código; redacción e idiomas fijados en el código, sin IA en ningún paso). plans lista los planes en que está disponible el feed, y usable indica si el plan de este espacio de trabajo es uno de ellos: un feed con usable false aparece en la lista para que se pueda ver, y suscribirle un canal se rechaza.

GET /v1/feeds/{feedId}/reference lista los clips de ejemplo que tiene un feed, por variante y versión, con los formatos en que viene cada uno. .../reference/{ratio} devuelve uno de ellos, un MP4 en lugar de JSON; acótelo con ?variant= y ?version= (sin ellos, la primera variante y la versión más reciente, y latest también es la más reciente), y la versión servida figura en x-preview-version. Una variante, versión o formato sin clip es un 404, y el etag funciona con If-None-Match como en el clip de referencia de una plantilla. Los clips de un feed muestran el estilo al que está vinculado; los demás estilos de la plantilla están en GET /v1/templates/{templateId}/reference.

PUT /v1/feeds activa o desactiva un feed para todo el espacio de trabajo. Admite sourceSlug, formatSlug y enabled, y enabled debe ser un booleano y no una cadena. Todo feed empieza activado, y eso por sí solo no envía nada: un canal todavía tiene que suscribirse a él. Un feed que no existe es un 404; uno que no se ofrece al espacio de trabajo y que ningún canal recibe es un 403.

curl -X PUT http://localhost:4000/v1/feeds \
-H "Authorization: Bearer rw_your_key_here" \
-H "Content-Type: application/json" \
-d '{"sourceSlug":"crypto-spot-and-dominance","formatSlug":"crypto-board","enabled":true}'
{ "sourceSlug": "crypto-spot-and-dominance", "formatSlug": "crypto-board", "enabled": true }

Desactivar un feed aquí lo detiene para todos los canales a la vez. Quitar la suscripción de un solo canal es una operación de canal; consulte Canales.

Conseguir que se publique un stream o un feed​

Ninguno de los dos publica nada hasta que un canal se suscribe a él: un stream con kind: "STREAM" y su streamId, un feed con kind: "BROADCAST" y su sourceSlug y formatSlug, ambos en POST /v1/channels/{id}/subscriptions; consulte Canales. La plantilla viene del stream o del feed, y el canal elige el corte y la compilación. Un feed que no se ofrece al espacio de trabajo es allí un 403.

Errores frecuentes​

Confundir /v1/stream con /v1/streams. Una recibe eventos, la otra los configura, y necesitan permisos distintos. La coincidencia de prefijos es exacta, así que una clave solo con ingest:write recibe un 403 en la ruta del plural, y una clave solo con streams:write no puede enviar.

Escribir un mapa de campos cuando el productor podría emitir directamente el formato correcto. Pregunte a /v1/formats/template/{templateId} qué acepta la plantilla. Un mapa existe para datos que usted no controla, no como la forma normal de entrada.

Anidar el mapa para reflejar el destino. Es plano. "/instrument/symbol" es una clave, no dos niveles de objeto.

Fijar una versión de plantilla en el stream. No se puede: el stream lleva la plantilla, y qué corte y qué compilación son una elección del canal que se suscribe. Consulte Canales.

Eliminar un stream para «reiniciarlo». Sus suscripciones terminan con él, cada canal que lo recibía deja de recibir, y el id no se puede volver a usar. Póngalo en pausa con enabled: false, que responde a los eventos con un 409 stream_disabled hasta que lo vuelva a activar.