Skip to content

Episódios pela API: criar, editar meta e buscar

Um sistema externo pode salvar o resultado do seu processamento no campo meta de um episódio. Por exemplo, um sistema de controle de acesso vincula um veículo reconhecido a uma solicitação e registra a decisão de abrir a cancela. O exemplo salva o ID de solicitação REQ-2026-001 e busca os episódios associados.

Antes de começar

Criar e atualizar exige permissão de escrita na API de gestão do central do Optic. Defina o endereço e as credenciais, e o nome de um stream existente. O papel viewer e um token de reprodução permitem apenas leitura. Os exemplos exigem Bash, curl e jq.

export CENTRAL_URL='https://central.example.com'
export API_LOGIN='admin'
export API_PASSWORD='your-password'
export STREAM='gate-entrance'

Habilite a retenção de episódios nas configurações DVR do stream ou do seu modelo. O central armazena os episódios quando o prazo total é positivo; a gravação de vídeo é opcional. O exemplo seguinte cria um episódio. Para um episódio existente, obtenha seu ID na lista e prossiga para a escrita de meta.

Criar um episódio

Sistemas externos chamam diretamente POST /central/api-v4/episodes: não precisam localizar o streamer que executa o stream. Crie um episódio fechado do tipo custom com access_control como tipo original.

O exemplo define opened_at e closed_at com os milissegundos Unix atuais, representando um evento instantâneo. Para eventos reais, envie seus tempos na linha do tempo do vídeo; eles podem diferir do momento de execução do comando. Omita closed_at para um episódio em andamento.

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

A primeira solicitação retorna 201 e {"episode_id":"…"}. Os comandos salvam o ID em EPISODE_ID para as solicitações PATCH e GET seguintes. O episódio fica disponível imediatamente no histórico e na aba Episódios do stream no Optic.

Repetir os mesmos media, opened_at e details.type retorna 200 e o ID original sem modificar o episódio. Para custom, a chave usa o tipo literal custom, não original_type. Reenvie o mesmo JSON ao tentar novamente: calcular um novo OPENED_AT significa um novo evento. POST não edita episódios existentes; use meta para dados adicionais.

Erros de criação:

  • 404 — stream desconhecido.
  • 409 — retenção de episódios desabilitada.
  • 422 — documento inválido, com caminhos de campos na resposta.
  • 413 — o corpo completo de POST excede 32 MiB.

Você pode incluir os dados originais do evento em payload. Use o campo separado meta para acréscimos posteriores de sistemas externos.

Salvar dados

Envie JSON Merge Patch para PATCH /central/api-v4/episodes/{episode_id}/meta. O exemplo agrupa os dados da integração em access_control; outros serviços podem usar suas próprias chaves.

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"

A resposta 200 contém o valor resultante de meta, que aceita JSON arbitrário. Objetos são mesclados recursivamente; arrays e escalares são substituídos por completo. Atualizar apenas decision preserva request_id. Alterações simultâneas em chaves diferentes são preservadas juntas; para a mesma chave, prevalece a última escrita confirmada.

Ler dados

Um GET normal do episódio retorna meta; este comando mostra apenas esse campo:

curl --fail-with-body --user "$API_LOGIN:$API_PASSWORD" \
  "$CENTRAL_URL/central/api-v4/episodes/$EPISODE_ID" | jq '.meta'

O campo também é retornado nas listas de episódios e na API playback do central ao ler com um token do stream. Enquanto meta não for definido, sua chave fica ausente do JSON do episódio. A aba Episódios do Optic mostra eventos e filtros padrão; os campos arbitrários de meta são lidos pela API.

Atualizar ou excluir dados

Para atualizar dados, envie PATCH com os novos valores. Para excluir uma chave, atribua null a ela. Esta solicitação remove a decisão e preserva o ID da solicitação:

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 remover todo o campo meta, envie --data 'null' na mesma solicitação. A resposta será JSON null, e as leituras seguintes do episódio voltarão a omitir a chave meta. Um objeto vazio {} é considerado um valor definido.

Buscar episódios por dados personalizados

O histórico do central aceita um filtro meta no servidor: passe uma estrutura JSON na query string de GET /central/api-v4/episodes. O meta salvo deve contê-la; campos adicionais são permitidos. Todas as chaves indicadas devem corresponder no nível correto de aninhamento. O filtro é aplicado antes do limite da página e combinado com os filtros padrão usando AND. O mesmo filtro está disponível na API playback do central. Ele não está disponível no feed atual do streamer nem no formulário de filtros da console do Optic.

Para o exemplo, escolha um stream, o período do evento e o ID da solicitação salvo. from e to são milissegundos Unix, com o limite superior excluído. Substitua os valores do exemplo pelo seu próprio intervalo.

export FROM_MS="$((OPENED_AT - 1000))"
export TO_MS="$((OPENED_AT + 60000))"
export REQUEST_ID='REQ-2026-001'

Solicite a primeira 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

Salve o código a seguir como find-episodes.sh e execute bash find-episodes.sh. Ele envia o filtro ao servidor, percorre as páginas de resultados e mostra um objeto JSON por episódio encontrado. Com curl --get, --data-urlencode é colocado na query string; GET não tem corpo.

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 decisões de abrir a cancela, use meta={"access_control":{"decision":"allow"}}. Para verificar a solicitação e a decisão juntas, indique ambas as chaves: meta={"access_control":{"request_id":"REQ-2026-001","decision":"allow"}}.

Os tipos JSON importam: a string "123" difere do número 123. Em arrays, verifica-se a presença dos elementos necessários: {"tags":["staff","delivery"]} corresponde a {"tags":["delivery","staff","urgent"]}. A ordem e as repetições não importam; um escalar também pode corresponder a um elemento de array. É uma busca pelo conteúdo, não pela igualdade do array inteiro.

Episódios sem meta não correspondem a nenhum filtro meta. Um objeto vazio {} corresponde a meta do tipo objeto, incluindo objetos não vazios; o filtro null não significa ausência do campo. JSON inválido ou U+0000 na query retorna 400. Não há sintaxe específica para intervalos de valores ou OR.

Siga next_cursor mantendo o filtro JSON original e os demais parâmetros. Uma página vazia com cursor não encerra a busca: ela pode indicar que um registro grande demais foi ignorado. offset não é suportado. O histórico pode mudar durante o percurso e não é uma captura fixa. PATCH de meta não altera updated_at: ordenar por esse tempo não permite acompanhar mudanças nos dados personalizados.

Retenção e erros

A coleta de um streamer e a repetição do POST de criação preservam meta. PATCH não altera o payload de origem, os tempos do evento ou o prazo de retenção. Os dados adicionais são removidos junto com o episódio; escrever meta não prolonga sua vida.

  • 404 — o episódio não foi encontrado ou já foi excluído.
  • 413 — o corpo da solicitação ou o meta serializado resultante excede 32 MiB.
  • 422 — JSON inválido ou U+0000; a resposta contém um erro estruturado.

Uma solicitação rejeitada preserva o valor anterior de meta.