Episodios mediante la API: crear, editar meta y buscar¶
Un sistema externo puede guardar el resultado de su procesamiento en el campo meta de un episodio. Por ejemplo, un sistema de control de acceso vincula un vehículo reconocido con una solicitud y guarda la decisión de abrir la barrera. El ejemplo guarda el ID de solicitud REQ-2026-001 y busca los episodios asociados.
Antes de empezar¶
Crear y actualizar requiere permiso de escritura en la API de gestión de central de Optic. Indique la dirección y las credenciales, y el nombre de un stream existente. El rol viewer y un token de reproducción solo permiten leer. Los ejemplos requieren Bash, curl y jq.
export CENTRAL_URL='https://central.example.com'
export API_LOGIN='admin'
export API_PASSWORD='your-password'
export STREAM='gate-entrance'
Habilite la retención de episodios en la configuración DVR del stream o de su plantilla. Central guarda los episodios cuando el plazo total es positivo; la grabación de vídeo es opcional. El siguiente ejemplo crea un episodio. Para uno existente, obtenga su ID de la lista y continúe con la escritura de meta.
Crear un episodio¶
Los sistemas externos llaman directamente a POST /central/api-v4/episodes: no necesitan localizar el streamer que ejecuta el stream. Cree un episodio cerrado de tipo custom con access_control como tipo original.
El ejemplo establece opened_at y closed_at en los milisegundos Unix actuales para representar un evento instantáneo. Para eventos reales, envíe sus tiempos en la línea temporal del vídeo; pueden diferir del momento de ejecución del comando. Omita closed_at para un episodio en curso.
OPENED_AT=$(( $(date +%s) * 1000 ))
episode=$(jq -n --arg media "$STREAM" --argjson opened_at "$OPENED_AT" '
{
media: $media,
opened_at: $opened_at,
closed_at: $opened_at,
details: {type: "custom", original_type: "access_control"}
}
')
created=$(curl --fail-with-body --silent --show-error \
--user "$API_LOGIN:$API_PASSWORD" \
--header 'Content-Type: application/json' \
--data "$episode" \
"$CENTRAL_URL/central/api-v4/episodes")
EPISODE_ID=$(printf '%s\n' "$created" | jq -er '.episode_id')
export EPISODE_ID
printf '%s\n' "$created" | jq
La primera solicitud devuelve 201 y {"episode_id":"…"}. Los comandos guardan el ID en EPISODE_ID para las solicitudes PATCH y GET siguientes. El episodio está disponible inmediatamente en el historial y en la pestaña Episodios del stream en Optic.
Repetir los mismos media, opened_at y details.type devuelve 200 y el ID original sin modificar el episodio. Para custom, la clave usa el tipo literal custom, no original_type. Reenvíe el mismo JSON al reintentar: calcular un nuevo OPENED_AT significa un evento nuevo. POST no edita episodios existentes; use meta para datos adicionales.
Errores de creación:
- 404 — stream desconocido.
- 409 — retención de episodios deshabilitada.
- 422 — documento no válido, con rutas de campos en la respuesta.
- 413 — el cuerpo completo de POST supera los 32 MiB.
Puede incluir los datos originales del evento en payload. Use el campo independiente meta para ampliaciones posteriores de sistemas externos.
Guardar datos¶
Envíe JSON Merge Patch a PATCH /central/api-v4/episodes/{episode_id}/meta. El ejemplo agrupa los datos de la integración bajo access_control; otros servicios pueden utilizar sus propias claves.
curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
--request PATCH \
--header 'Content-Type: application/merge-patch+json' \
--data '{"access_control":{"request_id":"REQ-2026-001","decision":"allow"}}' \
"$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID/meta"
La respuesta 200 contiene el valor resultante de meta, que admite JSON arbitrario. Los objetos se combinan recursivamente; los arrays y escalares se sustituyen por completo. Actualizar solo decision conserva request_id. Los cambios simultáneos en claves diferentes se conservan juntos; para la misma clave prevalece la última escritura confirmada.
Leer datos¶
Un GET normal del episodio devuelve meta; este comando muestra solo ese campo:
curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
"$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID" | jq '.meta'
El campo también se devuelve en las listas de episodios y en la API playback de central al leer con un token del stream. Mientras no se establezca meta, su clave está ausente del JSON del episodio. La pestaña Episodios de Optic muestra eventos y filtros estándar; los campos arbitrarios de meta se leen mediante la API.
Actualizar o eliminar datos¶
Para actualizar datos, envíe PATCH con los nuevos valores. Para eliminar una clave, asígnele null. Esta solicitud elimina la decisión y conserva el ID de solicitud:
curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
--request PATCH \
--header 'Content-Type: application/merge-patch+json' \
--data '{"access_control":{"decision":null}}' \
"$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID/meta"
Para eliminar todo meta, envíe --data 'null' en la misma solicitud. La respuesta será JSON null y las lecturas posteriores del episodio volverán a omitir la clave meta. Un objeto vacío {} se considera un valor establecido.
Buscar episodios por datos personalizados¶
El historial de central admite un filtro meta en el servidor: pase una estructura JSON en la query string de GET /central/api-v4/episodes. El meta guardado debe contenerla; se permiten campos adicionales. Todas las claves indicadas deben coincidir en el nivel de anidamiento correcto. El filtro se aplica antes del límite de página y se combina con los filtros estándar mediante AND. También está disponible en la API playback de central. No está disponible en el feed actual del streamer ni en el formulario de filtros de la consola Optic.
Para el ejemplo, elija un stream, el periodo del evento y el ID de solicitud guardado. from y to son milisegundos Unix y el límite superior está excluido. Sustituya los valores del ejemplo por su propio intervalo.
export FROM_MS="$((OPENED_AT - 1000))"
export TO_MS="$((OPENED_AT + 60000))"
export REQUEST_ID='REQ-2026-001'
Solicite la primera página:
curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" --get \
--data-urlencode "media=$STREAM" \
--data-urlencode "from=$FROM_MS" \
--data-urlencode "to=$TO_MS" \
--data-urlencode 'meta={"access_control":{"request_id":"REQ-2026-001"}}' \
"$CENTRAL_URL/central/api-v4/episodes" | jq
Guarde el siguiente código como find-episodes.sh y ejecute bash find-episodes.sh. Envía el filtro al servidor, recorre las páginas de resultados y muestra un objeto JSON por episodio coincidente. Con curl --get, --data-urlencode se coloca en la query string; GET no tiene cuerpo.
set -euo pipefail
filter=$(jq -cn --arg request_id "$REQUEST_ID" \
'{"access_control":{"request_id":$request_id}}')
cursor=''
while :; do
query=(--data-urlencode "media=$STREAM"
--data-urlencode "from=$FROM_MS"
--data-urlencode "to=$TO_MS"
--data-urlencode "meta=$filter"
--data-urlencode 'limit=1000')
if [ -n "$cursor" ]; then
query+=(--data-urlencode "cursor=$cursor")
fi
page=$(curl --fail-with-body --silent --show-error \
--user "$API_LOGIN:$API_PASSWORD" --get "${query[@]}" \
"$CENTRAL_URL/central/api-v4/episodes")
printf '%s\n' "$page" | jq -c \
'.episodes[] | {episode_id, media, opened_at, meta}'
cursor=$(printf '%s\n' "$page" | jq -r '.next_cursor // empty')
[ -n "$cursor" ] || break
done
Para buscar decisiones de abrir la barrera, use meta={"access_control":{"decision":"allow"}}. Para comprobar la solicitud y la decisión juntas, indique ambas claves: meta={"access_control":{"request_id":"REQ-2026-001","decision":"allow"}}.
Los tipos JSON importan: la cadena "123" difiere del número 123. En arrays se comprueba la presencia de los elementos requeridos: {"tags":["staff","delivery"]} coincide con {"tags":["delivery","staff","urgent"]}. El orden y las repeticiones no importan; un escalar también puede coincidir con un elemento de un array. Se comprueba el contenido, no la igualdad del array completo.
Los episodios sin meta no coinciden con ningún filtro meta. Un objeto vacío {} coincide con meta de tipo objeto, incluidos los objetos no vacíos; el filtro null no indica la ausencia del campo. JSON no válido o U+0000 en la query devuelve 400. No hay sintaxis específica para intervalos de valores ni OR.
Siga next_cursor conservando el filtro JSON original y los demás parámetros. Una página vacía con cursor no termina la búsqueda: puede indicar que se ha omitido un registro demasiado grande. offset no está admitido. El historial puede cambiar durante el recorrido y no es una instantánea fija. PATCH de meta no modifica updated_at: ordenar por ese tiempo no permite seguir los cambios de datos personalizados.
Retención y errores¶
La recolección desde un streamer y la repetición del POST de creación conservan meta. PATCH no cambia el payload de origen, los tiempos del evento ni el plazo de retención. Los datos adicionales se eliminan con el episodio; escribir meta no prolonga su vida.
- 404 — el episodio no existe o ya se ha eliminado.
- 413 — el cuerpo de la solicitud o el
metaserializado resultante supera los 32 MiB. - 422 — JSON no válido o U+0000; la respuesta contiene un error estructurado.
Una solicitud rechazada conserva el valor anterior de meta.