Cumplimiento
El registro de cumplimiento es una entrada por todo lo que ocurrió con las publicaciones de un espacio de trabajo una vez que lo abandonaron: cada publicación entregada, cada actualización, eliminación y reenvío, cada decisión de aprobación, el resultado de cada entrega, cada informe de en qué punto está una publicación y cada cambio en la aprobación de un canal. Cada entrada se escribe una vez y nunca se modifica, se sella en la cadena del espacio de trabajo, la firma Reelwire, una entidad externa le pone un sello de tiempo una vez al día y se conserva diez años, ocurra lo que ocurra desde entonces con el clip, la publicación o el canal. Para qué sirve y cómo lo usa una persona se explica en la página de la pantalla Cumplimiento.
El registro forma parte de Enterprise y registra desde el momento en que un espacio de trabajo está en él. Un espacio de trabajo que deja Enterprise no registra nada nuevo y sigue leyendo y exportando lo que tiene durante 90 días; después se elimina. En cualquier otro plan, leerlo es un 403 con una frase que lo dice. Cambiar las reglas de aprobación y añadir auditores es exclusivo de Enterprise, también un 403 durante esos 90 días; eliminar un auditor funciona en cualquier plan.
| Método | Ruta | Permiso |
|---|---|---|
GET | /v1/compliance | compliance:read |
GET | /v1/compliance/records | compliance:read |
GET | /v1/compliance/records/{id} | compliance:read |
GET | /v1/compliance/export | compliance:read |
GET | /v1/compliance/export/link | compliance:read |
GET | /v1/compliance-files/{tenantId} | solo el enlace firmado, sin credencial |
GET | /v1/compliance/audit | compliance:read |
PUT | /v1/compliance/approval-rules/{channelId} | persona con sesión iniciada: Aprobaciones |
GET | /v1/compliance/auditors | persona con sesión iniciada |
POST | /v1/compliance/auditors | persona con sesión iniciada: Administración |
POST | /v1/compliance/auditors/{userId}/resend | persona con sesión iniciada: Administración |
POST | /v1/compliance/auditors/{userId}/delete | persona con sesión iniciada: Administración |
Quién lo lee. Una clave con el permiso Cumplimiento, que es de solo lectura: una máquina puede leer el registro, comprobar un archivo con él y obtener la exportación para un archivo propio. Una persona con sesión iniciada con Aprobaciones o Administración (el propietario tiene ambos) y un auditor. Las reglas de aprobación y los auditores responden a una sesión del panel y nunca a una clave, porque unas aflojan un control y los otros dejan entrar a alguien: una clave recibe el rechazo «Sign in to do this» descrito en Autenticación.
El resumen
curl http://localhost:4000/v1/compliance \
-H "Authorization: Bearer rw_your_key_here"
{
"on": true,
"lapsed": null,
"retentionYears": 10,
"graceDays": 90,
"entries": 1284,
"sealed": 1283,
"waiting": 1,
"firstEntryAt": "2026-10-14T09:12:40.118Z",
"chain": { "seq": 1283, "headHash": "4f0c9a...e71d" },
"anchor": {
"day": "2026-10-15",
"seq": 1270,
"state": "STAMPED",
"genTime": "2026-10-15T00:01:12.000Z",
"tsaUrl": "http://timestamp.digicert.com"
},
"timeStampAuthority": "http://timestamp.digicert.com",
"keys": [
{ "id": "01M3B4Z9Q8R0J5V2C6N1T7K4WD", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\n...", "createdAt": "2026-10-14T09:12:41.002Z", "retired": false }
],
"kinds": [
{ "id": "post.publish", "label": "Handed over" },
{ "id": "post.update", "label": "Update handed over" }
]
}
Abreviado: kinds lista los ocho, y los hashes tienen 64 caracteres hexadecimales.
onindica si el espacio de trabajo registra ahora.lapsedesnullo, para un espacio de trabajo que dejó Enterprise,since(cuándo) ydeletesOn(cuándo se elimina lo que registró).entrieslas cuenta,sealedlas que tienen su número y su firma, ywaitinglas que se sellarán en los próximos segundos.chaines el número y el hash de la entrada más reciente.anchores el sello de tiempo diario más reciente que cubre este espacio de trabajo, hasta qué entrada (seq), y sustate:PENDINGmientras se vuelve a preguntar a la entidad, cada diez minutos aproximadamente,STAMPEDcon el momento en que firmó (genTime), oFAILEDcuando se le ha preguntado 48 veces sin respuesta.keysson las mitades públicas de las claves con las que Reelwire firma las entradas, en PEM.
Entradas
curl "http://localhost:4000/v1/compliance/records?kind=post.publish&perPage=1" \
-H "Authorization: Bearer rw_your_key_here"
{
"records": [
{
"id": "01M3B5C2D1E0F9G8H7J6K5M4N3",
"kind": "post.publish",
"label": "Handed over",
"occurredAt": "2026-10-15T08:30:02.411Z",
"summary": "Orchid Invest DE: Der DAX schloss 412 Punkte hoeher.",
"tone": "good",
"channels": ["Orchid Invest DE"],
"channelIds": ["01M1VFRWE7EWSY5D7QDQ0KNK1S"],
"postKey": "01M3B5BZQ1X0W9V8T7S6R5Q4P3",
"postIds": ["01M3B5C0A9B8C7D6E5F4G3H2J1"],
"videoSha256": ["9b2e1f...c04a"],
"seq": 1283,
"sealed": true,
"entryHash": "4f0c9a...e71d",
"frames": 3,
"stills": "DONE"
}
],
"total": 412,
"all": 1284,
"page": 1,
"perPage": 1,
"facets": { "kind": ["post.publish", "post.update", "..."], "channelId": ["01M1VFRWE7EWSY5D7QDQ0KNK1S"] },
"labels": { "kind": { "post.publish": "Handed over" }, "channelId": { "01M1VFRWE7EWSY5D7QDQ0KNK1S": "Orchid Invest DE" } }
}
Se pagina, busca y ordena como todo registro (consulte Listas y paginación), las más recientes primero salvo que ascending=true:
| Parámetro | Qué hace |
|---|---|
since, until | Solo las entradas ocurridas en esta ventana, instantes ISO 8601. |
kind | Un tipo o varios, separados por comas (abajo). |
channelId | Un canal o varios, separados por comas. |
sha256 | Solo las entradas que nombran este archivo de video, por los 64 caracteres hexadecimales de su SHA-256. Cualquier otra cosa es un 400. Consulte Comprobar un archivo de video. |
q | Una clave de publicación, un id de publicación, el hash de una entrada o palabras del texto de una publicación o del nombre de un canal. |
page, perPage | perPage es de 1 a 200, 25 por defecto. |
summary es la entrada en una línea, leída solo de la entrada, y tone cómo se lee de un vistazo (good, bad, waiting o plain). sealed es false durante los pocos segundos antes de que una entrada tenga su número (seq) y su hash. stills indica en qué punto están sus fotogramas: NONE para una entrada sin video propio, PENDING hasta que se toman, DONE o MISSING cuando el clip había salido antes del almacenamiento.
kind | Etiqueta | Se escribe cuando |
|---|---|---|
post.publish | Entregada | Una publicación salió por un canal. Una entrada por canal de una entrega. |
post.update | Actualización entregada | Se entregó un cambio en una publicación ya entregada. |
post.delete | Eliminación entregada | Se entregó una solicitud para retirar una publicación. |
post.resend | Enviada de nuevo | Un reenvío por correo electrónico, por webhook o a los participantes de sindicación, una republicación a través del conector de plataforma, o un reintento de una publicación que falló. |
approval.decision | Decisión de aprobación | Alguien aprobó o rechazó una publicación o una actualización, o dio la primera de dos aprobaciones. |
delivery.outcome | Resultado de la entrega | Un correo, una llamada de webhook o una publicación del conector de plataforma se entregó, o falló tras todos los intentos. |
post.report | Informada | Se informó de que una publicación se publicó (con su enlace), se omitió, no se publicó o se eliminó. |
channel.approval | Regla de aprobación cambiada | Se cambió la aprobación de un canal, o cambió su número de aprobadores. |
Una entrada
GET /v1/compliance/records/{id} responde con una entrada completa: lo que se registró, su sello, el sello de tiempo diario que la cubre y sus fotogramas.
{
"record": {
"id": "01M3B5C2D1E0F9G8H7J6K5M4N3",
"kind": "post.publish",
"label": "Handed over",
"occurredAt": "2026-10-15T08:30:02.411Z",
"summary": "Orchid Invest DE: Der DAX schloss 412 Punkte hoeher.",
"body": {
"v": 1,
"kind": "post.publish",
"occurredAt": "2026-10-15T08:30:02.411Z",
"workspace": { "id": "01M1VFRWE3A35H892VAAEKKDA6", "name": "Orchid Invest" },
"handover": { "id": "01M3B5C1...", "postKey": "01M3B5BZQ1X0W9V8T7S6R5Q4P3", "ways": ["EMAIL", "WEBHOOK"] },
"post": { "id": "01M3B5C0A9B8C7D6E5F4G3H2J1", "postGroupId": "01M3B5BZW..." },
"channel": { "id": "01M1VFRWE7EWSY5D7QDQ0KNK1S", "name": "Orchid Invest DE", "platform": "instagram", "surface": "instagram-reels", "ratio": "9x16", "locale": "de" },
"brand": "Orchid Invest",
"template": { "id": "tpl_dd7e6b264fb8", "variant": "Corporate glass", "version": "1.0.0", "build": "a41c...9e02" },
"source": { "kind": "queue", "queueName": "Market close", "title": "DAX close" },
"author": { "kind": "person", "id": "anna@orchid.example", "name": "Anna Berg" },
"ai": null,
"approval": {
"required": true,
"needed": 1,
"decisions": [{ "by": { "kind": "person", "id": "lena@orchid.example", "name": "Lena Kraus" }, "at": "2026-10-15T08:29:51.004Z", "note": "Figures checked." }]
},
"video": { "assetId": "01M3B5BZX...", "kind": "video", "ratio": "9x16", "mimeType": "video/mp4", "width": 1080, "height": 1920, "durationMs": 12000, "byteSize": 3481202, "sha256": "9b2e1f...c04a" },
"text": { "caption": "Der DAX schloss 412 Punkte hoeher.", "hashtags": ["dax", "boerse"], "disclaimer": "Keine Anlageberatung." }
},
"bodyHash": "c3d1...77aa",
"seal": {
"seq": 1283,
"prevHash": "8e91...02bd",
"entryHash": "4f0c9a...e71d",
"signature": "3vQ9...Bw==",
"keyId": "01M3B4Z9Q8R0J5V2C6N1T7K4WD",
"sealedAt": "2026-10-15T08:30:03.120Z"
},
"anchor": null,
"stills": "DONE",
"frames": [
{ "id": "01M3B5D...", "atMs": 1200, "width": 480, "height": 854, "url": "http://localhost:4000/v1/assets/01M3B5D.../download?exp=...&sig=...", "expiresAt": "2026-10-15T09:31:00.000Z" }
]
}
}
Abreviado: los tres fotogramas se toman a una décima parte, a la mitad y a nueve décimas partes del video, y sus enlaces funcionan durante una hora. anchor es null hasta que el siguiente sello de tiempo diario cubre la entrada y entonces es su day, state, genTime, tsaUrl y root. Un id que no es de una entrada de este espacio de trabajo es un 404.
Lo que lleva body depende de su tipo, y cada cuerpo indica su v, kind y occurredAt:
- Una entrega (
post.publish,post.resend): el espacio de trabajo, la entrega con susways(EMAIL,WEBHOOK,CONNECTOR,SYNDICATION) y, en un reenvío,again, que indica cuál; la publicación (conpermalinkdonde se conocía uno), el canal, la marca, la plantilla con la huella de subuild, elsource, elauthor(una persona, una clave de API ofeedpara un feed predefinido),ai(lo que hicieron los pasos de IA del servidor de feeds con el contenido de un feed predefinido, tal como lo indicó al enviarlo), laapprovalcon cada decisión, elvideoy lacovercon su SHA-256, eltext, cada longitud del texto en que se entregó (captions) y los ajustes de publicación. Un reenvío nombra quién lo pidió (requestedBy). - Una actualización: el mismo canal y publicación,
requestedBy, laapprovalpor la que pasó el cambio y elchangeen sí: su texto, sus textos y, donde sustituyó el video, el nuevovideo. - Una eliminación: el canal y la publicación, y
requestedBy. - Una decisión de aprobación:
approvalconabout(postoupdate),decision(approved,rejectedofirst-approval),needed,by,note,requestedBy(el autor) y, para una actualización, elchangeaprobado. - El resultado de una entrega:
deliverycon suway,outcome(deliveredofailed),attempts, eltargetdel webhook o de la plataforma, el últimostatusy unanote. - Un informe:
reportcon elstatede la publicación (PUBLISHED,SKIPPED,FAILEDoRECALLED),byy laurl,platformPostIdoreasonindicados. - Una regla de aprobación cambiada: el canal,
by, yfromyto, cada uno con suapprovalyapprovers.
Las personas se nombran por valor tal como eran en ese momento (kind, id como dirección de inicio de sesión o id de la clave, y name), de modo que un colega eliminado o una clave revocada desde entonces sigue nombrando quién lo hizo. El texto que vino de fuera del espacio de trabajo, el error de un receptor o un motivo que alguien escribió en un informe, se registra tal como llegó.
Comprobar un archivo de video
¿Qué publicación es este archivo? Calcule su SHA-256 donde está y pregunte:
sha256sum clip.mp4 # macOS: shasum -a 256 clip.mp4
curl "http://localhost:4000/v1/compliance/records?sha256=9b2e1f...c04a" \
-H "Authorization: Bearer rw_your_key_here"
Vuelve cada entrada que nombra ese archivo. Una coincidencia demuestra, hasta el byte, que es el archivo entregado. Una copia descargada de una plataforma nunca coincide, porque las plataformas recodifican lo que publican: la huella es del archivo que Reelwire entregó, el que recibió su receptor o su kit de publicación, que es el que hay que conservar.
La exportación
GET /v1/compliance/export responde con un ZIP para un período y, cuando se pide, algunos canales: lo que se entrega a un auditor. Es lo que descarga Descargar exportación para un auditor en la pantalla.
curl -o compliance.zip \
"http://localhost:4000/v1/compliance/export?since=2026-09-01T00:00:00Z&until=2026-09-30T23:59:59Z&channelId=01M1VFRWE7EWSY5D7QDQ0KNK1S" \
-H "Authorization: Bearer rw_your_key_here"
| Parámetro | Qué hace |
|---|---|
since, until | El período, instantes ISO 8601. until es por defecto ahora y since 30 días antes. Un período que empieza después de terminar es un 400. |
channelId | Solo algunos canales, separados por comas o repetidos. Si falta, son todos los canales. |
Responde con application/zip, con el nombre de archivo del espacio de trabajo y del período. Solo contiene entradas selladas; una escrita en los últimos segundos irá en la siguiente. Una exportación contiene como máximo 3000 entradas: un período con más es un 409 que indica cuántas contiene, así que pida un período más corto o menos canales y obtenga el resto por separado. Cada exportación se escribe en el registro de auditoría (compliance.exported), con el período y los canales.
| Archivo | Qué es |
|---|---|
report.pdf | El registro dispuesto para una persona, publicación por publicación, con tres fotogramas de cada video. Su primera página indica lo que encontró Reelwire al comprobar la exportación antes de escribirla. Describe; records.json demuestra. |
records.json | entries, cada una con su body, bodyHash, seq, prevHash, entryHash, signature y keyId; chain, cada eslabón desde la primera entrada del período hasta la más reciente que cubre un sello de tiempo diario; anchors, cada sello de tiempo diario con la prueba de este espacio de trabajo; y keys, las claves públicas que firmaron. |
anchors/ | Cada sello de tiempo diario (RFC 3161), tal como lo respondió la entidad, un archivo .tsr por día. |
stills/ | Los fotogramas que muestra el informe, nombrados por entrada y posición. |
verify.mjs | Cada comprobación de abajo, escrita. Node.js 18 o superior, nada que instalar. |
README.txt | Qué es todo ello y cómo comprobarlo. |
Un enlace a la exportación
GET /v1/compliance/export/link, con los mismos since, until y channelId, responde con una dirección firmada a ese ZIP en lugar del ZIP: para quien llama y no puede llevar el archivo a la persona que lo quiere, como un asistente de IA en una conexión alojada, que da a la persona el enlace para pulsar.
{
"url": "https://app.reelwire.io/v1/compliance-files/01M1V...?since=2026-09-01T00%3A00%3A00.000Z&until=...&link=01M...&exp=1792371234&sig=...",
"expiresAt": "2026-10-18T21:15:00.000Z",
"since": "2026-09-01T00:00:00.000Z",
"until": "2026-09-30T23:59:59.000Z",
"channels": []
}
La dirección no necesita credencial, porque es una, para esta exportación solamente, y funciona quince minutos. El período, los canales y el espacio de trabajo van firmados en ella: una dirección editada para llegar más atrás o a otro espacio de trabajo es un 403, y también lo es una que ha pasado su tiempo. El espacio de trabajo aún debe tener el registro cuando se usa el enlace. Pedir un enlace queda en el registro de auditoría (compliance.export_linked), y también cada descarga a través de él (compliance.exported, a nombre de quien pidió el enlace).
Verificar una exportación
Nada en ella pregunta a Reelwire. Desde la carpeta en la que se descomprimió:
node verify.mjs # every entry, the chain and the time stamps
node verify.mjs --clips ./videos # also: which entry each video in ./videos is
Imprime una línea por hallazgo, termina con «Everything checked holds.» o con cuántas comprobaciones fallaron, y sale con 1 cuando algo falló. Lo que comprueba, para quien escriba la suya:
-
El contenido de cada entrada.
bodyHashes el SHA-256, en hexadecimal, debodycomo JSON canónico: claves ordenadas en todos los niveles, nada indefinido, sin espacios. -
El sello de cada entrada.
entryHashes el SHA-256 del JSON canónico de un objeto que contiene el id del espacio de trabajo (tenantId,workspace.idenrecords.json),seq,prevHashybodyHash.signaturees la firma Ed25519 de Reelwire, en base64, sobre los 32 bytes deentryHash, comprobada con la clave dekeysque nombrakeyId. -
La cadena. Los números se suceden sin huecos, y el
prevHashde cada eslabón es elentryHashdel anterior. La primera entrada que un espacio de trabajo registró enlaza con 64 ceros. Una entrada cambiada, eliminada o intercalada después rompe todos los eslabones posteriores. -
Los sellos de tiempo diarios. Una vez por día UTC, la entrada más reciente de cada espacio de trabajo que registró algo se convierte en una hoja de un árbol de Merkle, y una entidad externa de sellado de tiempo firma su raíz. Una hoja es el SHA-256 de un byte 0 seguido de
tenantId,seqyheadHashunidos por saltos de línea; un nodo es el SHA-256 de un byte 1 seguido de los 32 bytes de cada uno de los dos hashes. Elproof.pathde cada ancla lista los hashes hermanos desde la hoja hacia arriba, con el lado (LoR) en que está cada uno, y lleva aroot. Una prueba nombra solo hashes, nunca otro espacio de trabajo. -
La firma propia de la entidad, con OpenSSL. El
rootdel ancla es el hash que firmó la entidad, así que se da como resumen, con un paquete de CA que contenga el certificado raíz de la entidad (el paquete propio de la mayoría de los sistemas lo tiene):openssl ts -verify -digest <root> -in anchors/<day>.tsr -CAfile <CA bundle>
openssl ts -reply -in anchors/<day>.tsr -text # what a time stamp saysLa entidad figura en cada ancla (
tsaUrl) y publica sus propios certificados.
Una entrada que lo supera fue escrita por Reelwire y no ha cambiado desde entonces. Una entrada que cubre un sello de tiempo existía, tal como es, en el momento en que firmó la entidad: nadie, Reelwire incluido, pudo escribirla o cambiarla después sin romper la cadena que lleva a ese sello. Las entradas posteriores al último sello de tiempo están selladas y firmadas, y esperan al siguiente.
El registro de auditoría
GET /v1/compliance/audit es el registro de auditoría propio del espacio de trabajo: quién cambió qué, a quién se añadió y con qué permisos, qué claves se crearon, por qué pasó cada publicación.
{
"entries": [
{
"id": "01M3B5E...",
"occurredAt": "2026-10-15T08:29:51.010Z",
"actor": "lena@orchid.example",
"actorKind": "person",
"action": "approval.decided",
"entityType": "ApprovalRequest",
"entityId": "01M3B5D7...",
"fromState": "PENDING",
"toState": "APPROVED",
"detail": { "kind": "POST", "posts": 1, "note": "Figures checked." }
}
],
"total": 37,
"all": 9120,
"page": 1,
"perPage": 25,
"facets": { "who": ["all"] }
}
Admite since, until, q (una acción, una persona o clave, o un id), page, perPage y ascending, como las entradas. Por defecto lista lo que hizo una persona o una clave (actorKind person o key); who=all añade los pasos propios de Reelwire con cada publicación (system), que son la mayor parte.
Reglas de aprobación
En Enterprise, un canal que pide aprobación puede pedir dos aprobadores: dos personas distintas con Aprobaciones aprueban cada una una publicación, o una actualización, antes de que salga. Si un canal pide aprobación es su approval en Canales; approvers ahí indica el número, y esto lo establece.
curl -X PUT http://localhost:4000/v1/compliance/approval-rules/01M1VFRWE7EWSY5D7QDQ0KNK1S \
-H "Authorization: Bearer rws_your_session" \
-H "Content-Type: application/json" \
-d '{"approvers":2}'
{ "channel": { "id": "01M1VFRWE7EWSY5D7QDQ0KNK1S", "name": "Orchid Invest DE", "approval": "PER_POST", "approvers": 2 } }
approvers es 1 o 2; cualquier otra cosa es un 400, y un canal que no es de este espacio de trabajo o que se eliminó, un 404. Necesita una persona con Aprobaciones, ya que reducirlo afloja un control. Se aplica a lo que empieza a esperar desde entonces; lo que ya espera conserva el número con el que se pidió. Un cambio se escribe en el registro de auditoría y es una entrada del registro.
Otra regla se cumple en Enterprise sin ajuste: nadie aprueba su propia publicación ni actualización. Consulte Aprobaciones para ver cómo aparecen ambas en /v1/approvals.
Auditores
Personas ajenas al espacio de trabajo que inician sesión para leer el registro, su exportación, el registro de auditoría y, sin cambiar nada, el resto del espacio de trabajo. No ocupan ningún usuario, no están en GET /v1/team/users, y un espacio de trabajo tiene como máximo 5. Quedan bloqueados mientras el espacio de trabajo no esté en Enterprise.
GET /v1/compliance/auditors responde con auditors, cada uno con id, email, displayName, lastLoginAt y createdAt, y max.
POST /v1/compliance/auditors con { "email": "auditor@example.com", "displayName": "Jane Doe" } (el nombre opcional, hasta 120 caracteres) añade uno y le envía el correo que recibe un colega, con un enlace para establecer su propia contraseña que no caduca. Responde 201 con el auditor y una password de un solo uso, que se muestra solo en esta respuesta. Un sexto auditor, o una dirección ya usada en cualquier parte de Reelwire, es un 409.
POST /v1/compliance/auditors/{userId}/resend envía un enlace nuevo, que sustituye al anterior; dentro del minuto siguiente al último es un 409. POST /v1/compliance/auditors/{userId}/delete cierra sus sesiones en todas partes, elimina su cuenta, se lo comunica por correo y responde { "removed": true }; lo que hizo queda en el registro de auditoría. Eliminar funciona en cualquier plan, de modo que un espacio de trabajo que dejó Enterprise todavía puede dar de baja a uno. Las rutas propias del equipo rechazan a un auditor con un 409 que apunta aquí.