API para usuários
A API v1 permite criar estudos Fast Track, importar estudos por ZIP, consultar o preparo do estudo e disparar execuções pelo Portal Myria.
Todos os endpoints ficam sob /api/v1/, retornam JSON e acessam apenas os dados da organização vinculada à chave de API. Requisições com corpo usam JSON, exceto o upload de ZIP, que usa multipart/form-data.
Última atualização desta página: 2026-06-11.
Como conseguir uma chave
No Portal, acesse Perfil → Chaves de API.
Crie uma chave com os escopos necessários:
| Escopo | Permite |
|---|---|
studies:write | Criar estudos Fast Track, enviar ZIPs, criar importações, adicionar RV+/M+ e alterar opções de rodadas. |
studies:read | Consultar importações e estudos. |
executions:write | Solicitar execuções. |
executions:read | Consultar execuções. |
O segredo completo aparece apenas uma vez, no momento da criação. Guarde-o em local seguro.
Se você não preencher Expiração, a chave vence automaticamente em 90 dias.
Quando a data é informada, a expiração ocorre no fim do dia escolhido. Depois,
você pode revogar a chave em Perfil → Chaves de API.
Formato da chave:
myria_live_<identificador>.<segredo>
Use a chave em todas as requisições:
Authorization: Bearer myria_live_<identificador>.<segredo>
Chaves expiradas, revogadas ou inválidas retornam 401. Chaves válidas sem escopo suficiente retornam 403. Muitas falhas de autenticação para o mesmo IP ou prefixo de chave retornam 429 rate_limited.
Limites de chamadas
Criações Fast Track, uploads, operações incrementais e execuções usam rate limit por chave e por organização. Os defaults são:
| Bucket | Criação de estudos | Uploads | Operações incrementais | Execuções |
|---|---|---|---|---|
| Chave | 30/hour | 30/hour | 60/hour | 60/hour |
| Organização | 300/hour | 300/hour | 600/hour | 600/hour |
Quando o limite é excedido, a resposta é 429 rate_limited com
Retry-After. Replays idempotentes de recursos já criados retornam o recurso
existente sem consumir novo slot de escrita.
Operadores configuram esses buckets com
MYRIA_API_V1_RATE_LIMIT_KEY_STUDIES,
MYRIA_API_V1_RATE_LIMIT_ORG_STUDIES,
MYRIA_API_V1_RATE_LIMIT_KEY_INCREMENTALS e
MYRIA_API_V1_RATE_LIMIT_ORG_INCREMENTALS, além dos buckets já existentes de
uploads e execuções.
Fluxo recomendado
- Para Novo Estudo operacional, chame
POST /api/v1/studiescomtemplate: "fast_track". Para importar deck próprio, envie o ZIP emPOST /api/v1/studies/imports. - Na criação Fast Track, guarde o
creation_ide ostudy_id. Na importação ZIP, guarde oimport_id. - Se importou ZIP, consulte
GET /api/v1/studies/imports/{import_id}até receber umstudy_id. - Consulte
GET /api/v1/studies/{study_id}atéready_for_executionsertrue. - Para adicionar rodadas, chame
POST /api/v1/studies/{study_id}/incremental-operationscommode: "rv"oumode: "m"e consulteGET /api/v1/studies/{study_id}/incremental-operations/{operation_id}. - Dispare
POST /api/v1/studies/{study_id}/executions. - Consulte
GET /api/v1/studies/{study_id}/executions/{execution_id}até a execução terminar.
Receita ponta a ponta sem usar a UI
- Crie o Fast Track com
POST /api/v1/studies, escopostudies:write,Content-Type: application/jsone body{"template": "fast_track"}. UseIdempotency-Keye guardecreation_id,study_idestudy_status_url. - Consulte
GET /api/v1/studies/{study_id}comstudies:readatéready_for_executionficartrueou o status chegar ablocked/failed. - Adicione RV+ com
POST /api/v1/studies/{study_id}/incremental-operationse body{"mode": "rv", "count": 1}. Adicione M+ com{"mode": "m"}. Ocountpode ir de1a24; RV+ na última RV do mês pode cruzar para M+ quando a regra operacional pedir isso. - Aguarde a preparação em
GET /api/v1/studies/{study_id}/incremental-operations/{operation_id}. Emcompleted, o camporodadastraz as rodadas criadas no mesmo formato deGET /api/v1/studies/{study_id}. - Ajuste corte, PREVS, binários e GEVAZP com o PATCH existente
PATCH /api/v1/studies/{study_id}/rodadas/{rodada_id_or_name}/options. Ele aceitacorte,prevs_file,prevs_source,decomp_base,binario_id,gevazp.binario_idegevazp.run_gevazp. - Execute o estudo inteiro com
POST /api/v1/studies/{study_id}/executions. Execução parcial, resume, reset, reexecução e prioridade explícita ainda retornam409 unsupported_execution_mode. - Consulte
GET /api/v1/studies/{study_id}/executions/{execution_id}até status terminal. Userequested_rodadaserun_id_snapshotpara saber quais rodadas pertencem à execução. - Leia o resultado com
GET /api/v1/studies/{study_id}/executions/{execution_id}/rodadas/{rodada_id_or_name}/outputs/deckoutput.json. O output só é retornado quando a execução está terminal, a rodada pertence ao snapshot e orun_idatual bate com orun_idregistrado pela execução.
Continuam iguais nesta versão: importação ZIP, consulta de estudo, PATCH de
rodada_options, execução de estudo inteiro, listagem e detalhe de execuções e
leitura de deckoutput.json. Outputs arbitrários, criação genérica por YAML,
execução parcial, resume e reset seguem fora do contrato público v1.
Idempotência
Chamadas mutáveis aceitam o cabeçalho Idempotency-Key: criações Fast Track,
importações, operações incrementais, execuções e o PATCH de opções de rodada.
Idempotency-Key: cliente-123-importacao-001
Use esse cabeçalho para repetir uma chamada com segurança em caso de timeout ou falha de rede. Repetir a mesma chamada com a mesma chave retorna o mesmo recurso, mesmo se o limite de chamadas já tiver sido excedido, e não consome novo slot de escrita.
Para criações Fast Track e importações, a chave de idempotência é isolada por
organização e usuário dono da chave de API. Para operações incrementais e
execuções, ela é isolada por estudo e usuário. Para PATCH de opções, ela é
isolada por estudo, rodada e usuário. Dois usuários da mesma organização podem
usar o mesmo valor sem
colisão, e o mesmo usuário pode reutilizar o valor em estudos ou rodadas
diferentes. Reusar a mesma chave com payload ou arquivo diferente dentro do
mesmo escopo retorna
409 idempotency_conflict.
Endpoints
POST /api/v1/studies
Cria um estudo operacional equivalente ao botão Novo Estudo do Portal usando o template Fast Track.
Escopo exigido: studies:write.
Request:
POST /api/v1/studies
Authorization: Bearer <chave>
Idempotency-Key: create-fast-track-20260609-001
Content-Type: application/json
{
"template": "fast_track",
"title": "Fast Track Junho 2026",
"org_slug": "minha-org"
}
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
template | texto | sim | Nesta versão, apenas "fast_track". |
title | texto | não | Título sugerido. Se omitido, a API usa o título descoberto pelo Fast Track e garante unicidade. Em ambientes com paths legados, o valor também precisa ser válido como raiz de path Gitea; paths absolutos, traversal, segmentos vazios e barras invertidas retornam 400 invalid_payload. |
org_slug | texto | não | Se enviado, deve ser igual à organização da chave. |
Resposta 202:
{
"success": true,
"creation_id": "create_6d0f1b7a8f8d4b70b5e9d4d8437628ef",
"study_id": 42,
"status": "created",
"template": "fast_track",
"options": {
"title": "Fast Track Junho 2026"
},
"study": {
"id": 42,
"title": "Fast Track Junho 2026",
"org_slug": "minha-org",
"status": "materializing",
"ready_for_execution": false,
"rodadas": [
{
"id": 101,
"name": "DC202606-sem1",
"type": "decomp",
"ready_for_execution": false
}
]
},
"study_status_url": "/api/v1/studies/42",
"errors": [],
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z"
}
O campo study usa o mesmo formato de GET /api/v1/studies/{study_id}.
Durante a materialização, consulte study_status_url até o estudo ficar pronto
ou bloqueado. Templates desconhecidos retornam 400 invalid_template. Falhas na
descoberta Fast Track retornam 503 fast_track_unavailable sem criar estudo
parcial e sem expor paths ou mensagens internas em error.details.reason. Se o
estudo já foi criado e o dispatch de materialização falhar após o commit, a
resposta continua sendo o recurso de criação com status: "created";
o cliente deve consultar study.status e study.materialization.status, que
podem indicar failed com mensagem pública de materialização.
POST /api/v1/studies/imports
Cria uma importação assíncrona de estudo a partir de um ZIP.
Escopo exigido: studies:write.
O arquivo deve ter extensão .zip, assinatura ZIP válida e respeitar o limite
configurado no Portal. O default é 134217728 bytes comprimidos. O servidor não
usa o MIME informado no multipart como critério de rejeição, e rejeita ZIPs com
metodos de compressao nao suportados, paths absolutos, traversal ..,
caracteres de controle em nomes de entrada, entradas criptografadas, colisões
case-insensitive ou conflitos entre arquivo e diretório. Entradas operacionais
de macOS, como __MACOSX, .DS_Store e nomes
iniciados por ._, são ignoradas antes da validação recursiva; ZIPs aninhados
reais continuam sendo validados. Na prática, um resource fork como
__MACOSX/estudo/._NW202602.zip não torna o upload inválido, mas um arquivo
.zip real fora de metadata ignorada ainda é aberto e validado.
Request:
POST /api/v1/studies/imports
Authorization: Bearer <chave>
Idempotency-Key: import-20260603-001
Content-Type: multipart/form-data
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
file | arquivo .zip | sim | ZIP do estudo. |
title | texto | não | Título sugerido para o estudo. |
titulo | texto | não | Alias aceito para title. |
org_slug | texto | não | Se enviado, deve ser igual à organização da chave. |
selected_rodadas | lista, JSON ou texto | não | Subconjunto de rodadas a importar. Aceita campo repetido, selected_rodadas[], array JSON string ou lista separada por vírgulas. |
execution_options | JSON object | não | Reservado para opções futuras. Campos internos ainda não são aceitos. |
rodada_options | JSON object | não | Opcoes por rodada da Fase 1. A API aplica o bloco antes do readiness e devolve options.rodada_options normalizado. |
Exemplo com rodada_options:
curl -sS -X POST "$MYRIA_URL/api/v1/studies/imports" \
-H "Authorization: Bearer $MYRIA_API_KEY" \
-H "Idempotency-Key: import-20260608-rodada-options-001" \
-F "file=@deck.zip;type=application/zip" \
-F "title=DECOMP PMO 202606 RV0" \
-F "selected_rodadas[]=decomp_rv0" \
-F 'rodada_options={"decomp_rv0":{"corte":{"gitea_path":"estudos/estudo_120/rodada_991","gitea_repo":"estudos","org_slug":"minha-org"},"prevs_file":"prevs.rv0"}}'
Resposta 202:
{
"success": true,
"import_id": "imp_6d0f1b7a8f8d4b70b5e9d4d8437628ef",
"study_id": null,
"status": "uploaded",
"retryable": true,
"message": "ZIP recebido para processamento.",
"file": {
"sha256": "b1946ac92492d2347c6235b4d2611184",
"size_bytes": 123456
},
"options": {
"title": "DECOMP PMO 202606 RV0",
"selected_rodadas": [
"decomp_rv0"
],
"execution_options": {},
"rodada_options": {
"decomp_rv0": {
"corte": {
"org_slug": "minha-org",
"gitea_repo": "estudos",
"gitea_path": "estudos/estudo_120/rodada_991"
},
"prevs_file": "prevs.rv0"
}
}
},
"errors": [],
"study_status_url": null,
"import_status_url": "/api/v1/studies/imports/imp_6d0f1b7a8f8d4b70b5e9d4d8437628ef",
"created_at": "2026-06-03T12:00:00Z",
"updated_at": "2026-06-03T12:00:00Z",
"started_at": null,
"finished_at": null
}
O payload de resposta inclui options.rodada_options normalizado.
GET /api/v1/studies/imports/{import_id}
Consulta o status da importação.
Escopo exigido: studies:read.
Estados possíveis:
| Status | Significado |
|---|---|
uploaded | ZIP recebido e aguardando processamento. |
parsing | ZIP em leitura/normalização. |
materializing | Arquivos sendo gravados no Gitea. |
validating | Readiness sendo calculado. |
ready | Estudo pronto para execução. |
blocked | Estudo criado com pendências bloqueantes. |
failed | Falha na importação ou validação. |
O campo errors usa códigos e mensagens públicas estáveis. Ele não inclui
tracebacks, caminhos internos, mensagens de broker ou detalhes de backend.
Quando study_id estiver preenchido, use study_status_url para consultar o estudo.
O campo retryable é true para jobs ainda em uploaded e para falhas de
despacho Celery (dispatch_failed). Falhas de parse, materialização ou
readiness exigem nova importação ou correção operacional.
GET /api/v1/studies/{study_id}
Consulta um estudo.
Escopo exigido: studies:read.
Resposta:
{
"success": true,
"study": {
"id": 42,
"title": "DECOMP PMO 202606 RV0",
"org_slug": "minha-org",
"status": "ready",
"ready_for_execution": true,
"origin": "importado",
"gitea_repo": "estudos",
"gitea_path": "estudo_42",
"organization": {
"id": 3,
"slug": "minha-org"
},
"validation": {
"status": "valid",
"raw_status": "completed",
"is_valid": true,
"errors": []
},
"materialization": {
"id": 77,
"kind": "zip_import",
"status": "completed",
"completed": true,
"retryable": false,
"gitea_org": "org-minha-org",
"gitea_repo": "estudos",
"gitea_path": "estudo_42",
"path_scope": "user_study",
"error_message": "",
"metadata": {},
"created_at": "2026-06-03T11:59:00Z",
"updated_at": "2026-06-03T12:00:00Z"
},
"rodadas": [
{
"id": 434,
"name": "decomp_rv0",
"order": 0,
"type": "decomp",
"rv": "rv0",
"status": "pending",
"ready_for_execution": true,
"readiness_status": "ready",
"readiness": {},
"validation_status": "valid",
"validation": {
"is_valid": true,
"errors": []
},
"execution_status": "idle",
"effective_execution_options": {},
"run_id": null,
"dispatch_state": null,
"execution_steps": {},
"errors": [],
"updated_at": "2026-06-03T12:00:00Z"
}
],
"created_at": "2026-06-03T12:00:00Z",
"updated_at": "2026-06-03T12:00:00Z"
}
}
Estados consolidados do estudo:
| Status | Significado |
|---|---|
materializing | Arquivos ainda estão sendo gravados. |
validating | Validação/readiness ainda está em andamento. |
ready | Estudo pronto para execução. |
blocked | Há pendências bloqueantes. |
failed | Houve falha operacional ou validação falhou. |
O status ready usa a mesma regra de elegibilidade da execução: pendências que
dependem apenas de rodadas internas da cadeia não bloqueiam o disparo quando há
uma rodada inicial pronta. Readiness unknown permanece como validating na
API.
Mensagens em validation.errors são mensagens públicas; falhas internas de
worker, traceback, paths locais e detalhes de backend são substituídos por uma
mensagem genérica.
POST /api/v1/studies/{study_id}/incremental-operations
Cria uma operação incremental assíncrona para adicionar RV+ ou M+ a um estudo operacional.
Escopo exigido: studies:write.
Request:
POST /api/v1/studies/42/incremental-operations
Authorization: Bearer <chave>
Idempotency-Key: incr-20260609-001
Content-Type: application/json
{
"mode": "rv",
"count": 1
}
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
mode | texto | sim | "rv" para RV+ ou "m" para M+. |
count | inteiro | não | Quantidade de incrementos, entre 1 e 24. Default: 1. |
Resposta 202:
{
"success": true,
"operation_id": "incr_6d0f1b7a8f8d4b70b5e9d4d8437628ef",
"study_id": 42,
"status": "pending",
"mode": "rv",
"count": 1,
"created": 0,
"rodada_ids": [],
"rodadas": [],
"options": {
"mode": "rv",
"count": 1
},
"error": null,
"study_status_url": "/api/v1/studies/42",
"operation_status_url": "/api/v1/studies/42/incremental-operations/incr_6d0f1b7a8f8d4b70b5e9d4d8437628ef",
"created_at": "2026-06-09T12:00:00Z",
"updated_at": "2026-06-09T12:00:00Z",
"started_at": null,
"finished_at": null
}
Se o broker não retornar task_id ou o dispatch levantar exceção depois de a
operação ser persistida, o POST ainda retorna 202 com o próprio recurso da
operação: success: true, status: "failed" e error.code: "incremental_operation_failed".
O GET posterior retorna o mesmo formato com HTTP 200. Conflitos de execução,
materialização ativa, operação incremental
ativa, deck compartilhado ou recurso de outra organização retornam erros
públicos sem traceback.
GET /api/v1/studies/{study_id}/incremental-operations/{operation_id}
Consulta uma operação incremental pelo operation_id retornado no POST.
Escopo exigido: studies:read.
A resposta usa o mesmo shape do POST. Quando a operação criou rodadas, o campo
rodadas usa o mesmo formato das rodadas de GET /api/v1/studies/{study_id}.
Status possíveis da operação:
| Status | Significado |
|---|---|
pending | Operação aceita e aguardando preparação no worker. |
running | Rodadas incrementais foram criadas e estão em montagem/materialização. |
completed | Preparação e montagem terminaram. |
failed | Preparação, dispatch ou montagem falhou; consulte error. |
cancelled | Operação cancelada por rotina operacional. |
PATCH /api/v1/studies/{study_id}/rodadas/{rodada_id_or_name}/options
PATCH /api/v1/studies/{study_id}/rodadas/{rodada_id_or_name}/options
Atualiza as opcoes de uma rodada de um estudo ja materializado.
Escopo exigido: studies:write.
O body JSON deve conter apenas os campos da rodada. O endpoint resolve
{rodada_id_or_name} por id ou nome, retorna 400 unknown_rodada quando nao
consegue resolver a rodada, 409 ambiguous_rodada quando o nome e ambiguo e
409 study_conflict quando o estudo esta compartilhado ou em mutacao ativa.
As mesmas regras de allow-list da API v1 se aplicam aqui: corte, PREVS, base DECOMP, binarios e flag GEVAZP sao aplicados dentro de uma transacao, antes de recalcular readiness e sincronizar o YAML do estudo.
Campos de Fase 1 aceitos neste PATCH:
corteprevs_fileprevs_sourcedecomp_basedecomp_base_earm_columnbinario_idgevazp.binario_idgevazp.run_gevazp
Exemplo:
curl -sS -X PATCH "$MYRIA_URL/api/v1/studies/$STUDY_ID/rodadas/decomp_rv0/options" \
-H "Authorization: Bearer $MYRIA_API_KEY" \
-H "Idempotency-Key: patch-20260608-rodada-options-001" \
-H "Content-Type: application/json" \
-d '{
"corte": {
"gitea_path": "estudos/estudo_120/rodada_991",
"gitea_repo": "estudos",
"org_slug": "minha-org"
},
"prevs_file": "prevs.rv0"
}'
Resposta: o mesmo shape de GET /api/v1/studies/{study_id} com as rodadas
atualizadas.
Alterando o EARM de entrada de uma rodada DECOMP
Para executar um DECOMP importado sem rematerializar o deck com monta_rv0 ou
monta_rvx, mas usando EARM inicial vindo de outro deck, configure
rodada_options.<rodada>.decomp_base. Esse campo aponta para o deck DECOMP que o
preflight deve usar como fonte de EARM.
Durante o preflight, a API procura sumario.* no decomp_base; se nao houver
EARM por usina no sumario, tenta relato.*. Em seguida, atualiza apenas o campo
VINI das linhas UH ja existentes no dadger da rodada que sera executada. O
bloco UH nao e copiado integralmente do deck fonte.
Por padrao, o preflight mantem a regra operacional atual: para rv0, usa a
penultima coluna numerica do sumario; para rv1+, usa Sem_01. No fallback
por relato, usa o valor de fim da primeira semana. Para forcar o EARM inicial
do deck fonte, envie decomp_base_earm_column: "inic" junto com decomp_base;
nesse caso a API usa a coluna Inic. do sumario ou a coluna inicial da tabela
de volume por usina no relato.
Exemplo aplicando a mudanca em um estudo ja importado:
curl -sS -X PATCH "$MYRIA_URL/api/v1/studies/$STUDY_ID/rodadas/decomp_rv0/options" \
-H "Authorization: Bearer $MYRIA_API_KEY" \
-H "Idempotency-Key: patch-earm-decomp-base-001" \
-H "Content-Type: application/json" \
-d '{
"decomp_base": {
"gitea_path": "estudos/estudo_120/rodada_991",
"gitea_repo": "estudos",
"org_slug": "minha-org"
},
"decomp_base_earm_column": "inic"
}'
O mesmo bloco pode ser enviado na importacao do ZIP:
curl -sS -X POST "$MYRIA_URL/api/v1/studies/imports" \
-H "Authorization: Bearer $MYRIA_API_KEY" \
-H "Idempotency-Key: import-earm-decomp-base-001" \
-F "file=@deck.zip;type=application/zip" \
-F "title=Estudo com EARM ajustado" \
-F 'rodada_options={
"decomp_rv0": {
"decomp_base": {
"gitea_path": "estudos/estudo_120/rodada_991",
"gitea_repo": "estudos",
"org_slug": "minha-org"
},
"decomp_base_earm_column": "inic"
}
}'
Tambem e possivel enviar decomp_base no rodada_options do POST de execucao,
desde que isso seja feito antes do dispatch da rodada:
{
"rodada_options": {
"decomp_rv0": {
"decomp_base": {
"gitea_path": "estudos/estudo_120/rodada_991",
"gitea_repo": "estudos",
"org_slug": "minha-org"
},
"decomp_base_earm_column": "inic"
}
}
}
Use rodada apenas quando quiser apontar para outra rodada do mesmo estudo. Para
forcar um path especifico no Gitea, envie gitea_path sem rodada; quando
rodada e informado, a API resolve o path a partir da rodada referenciada.
Observacoes:
decomp_baseso se aplica a rodadasdecomp.decomp_base_earm_columnaceita apenas"inic"ounull; campo ausente preserva a regra operacional atual.- O deck fonte precisa conter
sumario.*ourelato.*com EARM por usina. - Se uma UHE do
dadgeralvo nao existir na fonte, o valor atual dessa linha e mantido. - Regras de preflight (
preflight_rulesouregras_volume_uhe.dat) rodam depois desse ajuste e podem sobrescrever volumesUHespecificos.
POST /api/v1/studies/{study_id}/executions
Solicita execução do estudo.
Escopo exigido: executions:write.
Na versão atual, a API executa o estudo inteiro. Execução parcial, reexecução, resume e reset ainda não são suportados pela API v1.
Se você enviar rodadas, requested_rodadas, action, mode ou priority_main_runs, a API retorna 409 unsupported_execution_mode.
A execução exige estudo válido, ao menos um servidor ativo na organização e readiness suficiente para iniciar a cadeia. Pendências puramente encadeadas entre rodadas internas não bloqueiam quando há uma rodada inicial pronta; sensibilidades com pendências bloqueantes impedem o disparo pela API v1.
rodada_options e aplicado dentro da transacao antes do readiness e antes do
dispatch. A mutacao de Rodada.config, Rodada.binario e Estudo.yaml_content
e permanente quando a execucao e aceita. Se a aplicacao das opcoes falhar,
nenhuma StudyExecutionRequest e criada. Se as opcoes forem aplicadas mas o
inicio da execucao falhar, a request fica failed e as mutacoes das opcoes sao
restauradas.
Opções aceitas no body:
| Campo | Tipo | Descrição |
|---|---|---|
org_slug | texto | Opcional. Se enviado, deve ser igual à organização da chave; divergência retorna 403 organization_mismatch. |
execution_options.decomp_cpu_cores | inteiro ou null | Cores a reservar para cada rodada DECOMP. Alias: processors. Valor explícito exige ao menos um servidor ativo com capacidade de CPU conhecida e não pode exceder a maior capacidade conhecida. |
execution_options.decomp_max_inviab_attempts | inteiro 1..20 ou null | Máximo de execuções internas do tratamento de inviabilidades DECOMP. Alias: infeasibility_treatment_attempts. |
rodada_options | objeto | Opcoes por rodada da Fase 1. A API aplica o bloco antes do readiness e devolve options.rodada_options normalizado. |
null limpa a configuração do estudo. Sem valor explícito, o DECOMP usa o default operacional atual.
Request:
POST /api/v1/studies/42/executions
Authorization: Bearer <chave>
Idempotency-Key: exec-20260603-001
Content-Type: application/json
Body:
{
"execution_options": {
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7
},
"rodada_options": {
"decomp_rv0": {
"prevs_file": "prevs.rv0"
}
}
}
Resposta 202:
{
"success": true,
"execution_id": "exec_73fd5b985c6a4b629e2e5411bbd4bd12",
"study_id": 42,
"status": "running",
"requested_rodadas": [
{
"id": 434,
"name": "decomp_rv0",
"order": 0,
"type": "decomp"
}
],
"rodadas": [
{
"id": 434,
"name": "decomp_rv0",
"order": 0,
"type": "decomp",
"status": "running",
"execution_status": "running",
"run_id": "a1b2c3d4",
"dispatch_state": "started",
"execution_steps": {
"Execucao": "running"
},
"server": {
"id": 7,
"name": "srv-prod-1"
},
"effective_execution_options": {
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7
},
"errors": [],
"updated_at": "2026-06-03T12:05:02Z"
}
],
"options": {
"execution_options": {
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7
},
"rodada_options": {
"decomp_rv0": {
"prevs_file": "prevs.rv0"
}
}
},
"run_id_snapshot": {
"434": {
"rodada_id": 434,
"name": "decomp_rv0",
"status": "running",
"run_id": "a1b2c3d4",
"dispatch_state": "started"
}
},
"errors": [],
"execution_status_url": "/api/v1/studies/42/executions/exec_73fd5b985c6a4b629e2e5411bbd4bd12",
"created_at": "2026-06-03T12:05:00Z",
"updated_at": "2026-06-03T12:05:02Z",
"started_at": "2026-06-03T12:05:02Z",
"finished_at": null
}
Se a execução com chave de idempotência falhar antes de enfileirar qualquer rodada,
o recurso final pode retornar status: "failed" com requested_rodadas: [],
rodadas: [] e run_id_snapshot: {}.
GET /api/v1/studies/{study_id}/executions
Lista execuções de um estudo em ordem decrescente de criação.
Escopo exigido: executions:read.
Parâmetros de query:
| Parâmetro | Default | Descrição |
|---|---|---|
limit | 50 | Quantidade máxima de execuções retornadas. Aceita 1..100. |
offset | 0 | Quantidade de execuções a pular. Deve ser maior ou igual a zero. |
Resposta:
{
"success": true,
"executions": [
{
"success": true,
"execution_id": "exec_73fd5b985c6a4b629e2e5411bbd4bd12",
"study_id": 42,
"status": "running",
"requested_rodadas": [
{
"id": 434,
"name": "decomp_rv0",
"order": 0,
"type": "decomp"
}
],
"rodadas": [
{
"id": 434,
"name": "decomp_rv0",
"order": 0,
"type": "decomp",
"status": "running",
"execution_status": "running",
"run_id": "a1b2c3d4",
"dispatch_state": "started",
"execution_steps": {
"Execucao": "running"
},
"server": {
"id": 7,
"name": "srv-prod-1"
},
"effective_execution_options": {
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7
},
"errors": [],
"updated_at": "2026-06-03T12:05:02Z"
}
],
"options": {
"execution_options": {
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7
},
"rodada_options": {
"decomp_rv0": {
"prevs_file": "prevs.rv0"
}
}
},
"run_id_snapshot": {
"434": {
"rodada_id": 434,
"name": "decomp_rv0",
"status": "running",
"run_id": "a1b2c3d4",
"dispatch_state": "started"
}
},
"errors": [],
"execution_status_url": "/api/v1/studies/42/executions/exec_73fd5b985c6a4b629e2e5411bbd4bd12",
"created_at": "2026-06-03T12:05:00Z",
"updated_at": "2026-06-03T12:05:02Z",
"started_at": "2026-06-03T12:05:02Z",
"finished_at": null
}
],
"pagination": {
"limit": 50,
"offset": 0,
"count": 1,
"total": 1,
"next_offset": null,
"previous_offset": null
}
}
Cada item da lista usa o mesmo formato do endpoint de detalhe
GET /api/v1/studies/{study_id}/executions/{execution_id}.
GET /api/v1/studies/{study_id}/executions/{execution_id}
Consulta uma execução específica.
Escopo exigido: executions:read.
Estados possíveis:
| Status | Significado |
|---|---|
queued | Execução solicitada e aguardando servidor ou worker. |
running | Pelo menos uma rodada está ativa. |
completed | Todas as rodadas terminaram com sucesso. |
nonconverged | Rodadas terminaram sem convergência ou com mistura de sucesso e não convergência. |
failed | Falha operacional. |
stopped | Execução parada. |
partial | Mistura de estados terminais. |
Rodadas changed são tratadas como terminal não enfileirado: todas changed
consolidam como stopped; mistura de completed com changed consolida como
partial.
GET /api/v1/studies/{study_id}/executions/{execution_id}/rodadas/{rodada_id_or_name}/outputs/deckoutput.json
GET /api/v1/studies/{study_id}/executions/{execution_id}/rodadas/{rodada_id_or_name}/outputs/deckoutput.json
Lê o deckoutput.json gerado para uma rodada rastreada por uma execução da API
v1. Este endpoint retorna apenas o output estruturado deckoutput.json; outros
arquivos da rodada continuam disponíveis pelo Gitea/Git.
Escopo exigido: executions:read.
rodada_id_or_name aceita o id numérico da rodada ou o nome exato.
curl -sS \
"$MYRIA_URL/api/v1/studies/$STUDY_ID/executions/$EXECUTION_ID/rodadas/DC202605-sem3/outputs/deckoutput.json" \
-H "Authorization: Bearer $MYRIA_API_KEY"
Resposta:
{
"success": true,
"study_id": 201,
"execution_id": "exec_aa996401c6984767a4cc4e46cc4afc19",
"rodada": {
"id": 1529,
"name": "DC202605-sem3",
"type": "decomp",
"status": "completed",
"run_id": "1f1836ab3ae748b88b00f095c5ab8c3b"
},
"output": {
"name": "deckoutput.json",
"content_type": "application/json",
"size_bytes": 12345,
"gitea": {
"org": "org-teko",
"repo": "estudos",
"path": "estudo_201/rodada_1529/deckoutput.json",
"ref": "main"
},
"content": {
"PLD": {},
"earm": {},
"ena": {
"SE": {
"Sem_01": 27789.09,
"Sem_02": 25662.11,
"Sem_03": 27606.72,
"Sem_04": 27305.28,
"Sem_05": 24930.24,
"Sem_06": 22997.70
},
"S": {
"Sem_01": 7630.03,
"Sem_02": 5217.11,
"Sem_03": 6359.73,
"Sem_04": 7015.60,
"Sem_05": 9735.19,
"Sem_06": 10424.62
},
"NE": {
"Sem_01": 4239.86,
"Sem_02": 3936.92,
"Sem_03": 3727.83,
"Sem_04": 3548.65,
"Sem_05": 3524.96,
"Sem_06": 3558.38
},
"N": {
"Sem_01": 10009.70,
"Sem_02": 8189.19,
"Sem_03": 6876.42,
"Sem_04": 5810.95,
"Sem_05": 4881.93,
"Sem_06": 4139.34
}
}
}
}
}
Regras principais:
- em rodadas DECOMP,
enae calculada a partir deprevs.rvX,REGRAS.DAT,hidr.date da produtividade do estagio 1 derelato.rvX; - a ENA e agregada por submercado e sempre usa
Sem_01aSem_06, independentemente da revisao; - se o calculo de ENA falhar, o restante do
deckoutput.jsoncontinua disponivel,enaretorna{}ediagnosticos.enainforma o motivo; - a execução precisa estar em status terminal (
completed,nonconverged,failed,stoppedoupartial); - a rodada precisa pertencer ao snapshot da execução;
- se o snapshot da execução tem
run_id, ele precisa corresponder aorun_idatual da rodada; a API não retorna output de uma reexecução posterior; - o path do arquivo é derivado da rodada; o cliente não envia path livre;
- o arquivo é retornado inline até o limite operacional
MYRIA_API_V1_OUTPUT_MAX_BYTES; - se o arquivo não existe ou não corresponde ao
run_idda execução, a API retorna404 output_not_found.
Exemplos em Python
Os exemplos abaixo usam requests.
pip install requests
Cliente básico
import os
import time
from pathlib import Path
import requests
BASE_URL = os.environ["MYRIA_URL"].rstrip("/")
API_KEY = os.environ["MYRIA_API_KEY"]
class MyriaClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({
"Authorization": f"Bearer {api_key}",
})
def _raise_for_api_error(self, response: requests.Response):
if response.ok:
return
try:
payload = response.json()
except ValueError:
response.raise_for_status()
error = payload.get("error", {})
code = error.get("code", "unknown_error")
message = error.get("message", response.text)
raise RuntimeError(f"{response.status_code} {code}: {message}")
def import_zip(self, zip_path: str, *, title: str, idempotency_key: str):
url = f"{self.base_url}/api/v1/studies/imports"
with Path(zip_path).open("rb") as file_obj:
response = self.session.post(
url,
headers={"Idempotency-Key": idempotency_key},
files={"file": (Path(zip_path).name, file_obj, "application/zip")},
data={"title": title},
timeout=60,
)
self._raise_for_api_error(response)
return response.json()
def get_import(self, import_id: str):
response = self.session.get(
f"{self.base_url}/api/v1/studies/imports/{import_id}",
timeout=30,
)
self._raise_for_api_error(response)
return response.json()
def get_study(self, study_id: int):
response = self.session.get(
f"{self.base_url}/api/v1/studies/{study_id}",
timeout=30,
)
self._raise_for_api_error(response)
return response.json()
def start_execution(self, study_id: int, *, idempotency_key: str):
response = self.session.post(
f"{self.base_url}/api/v1/studies/{study_id}/executions",
headers={
"Idempotency-Key": idempotency_key,
"Content-Type": "application/json",
},
json={},
timeout=30,
)
self._raise_for_api_error(response)
return response.json()
def get_execution(self, study_id: int, execution_id: str):
response = self.session.get(
f"{self.base_url}/api/v1/studies/{study_id}/executions/{execution_id}",
timeout=30,
)
self._raise_for_api_error(response)
return response.json()
Importar um ZIP e aguardar o estudo ficar pronto
client = MyriaClient(BASE_URL, API_KEY)
job = client.import_zip(
"decomp_202606_rv0.zip",
title="DECOMP PMO 202606 RV0",
idempotency_key="import-decomp-202606-rv0",
)
import_id = job["import_id"]
while True:
job = client.get_import(import_id)
print("import status:", job["status"])
if job["status"] == "failed":
raise RuntimeError(job["errors"])
if job["study_id"]:
study_id = job["study_id"]
break
time.sleep(10)
while True:
study = client.get_study(study_id)["study"]
print("study status:", study["status"])
if study["ready_for_execution"]:
break
if study["status"] in {"blocked", "failed"}:
raise RuntimeError(study["validation"]["errors"])
time.sleep(10)
print("study_id:", study_id)
Disparar execução e aguardar terminar
execution = client.start_execution(
study_id,
idempotency_key=f"exec-study-{study_id}-001",
)
execution_id = execution["execution_id"]
terminal_statuses = {
"completed",
"nonconverged",
"failed",
"stopped",
"partial",
}
while True:
execution = client.get_execution(study_id, execution_id)
print("execution status:", execution["status"])
if execution["status"] in terminal_statuses:
break
time.sleep(15)
if execution["status"] != "completed":
print("errors:", execution["errors"])
for rodada in execution["rodadas"]:
print(rodada["name"], rodada["status"], rodada.get("run_id"))
Erros
Todos os erros seguem este formato:
{
"success": false,
"error": {
"code": "invalid_zip",
"message": "Arquivo deve ser um ZIP valido.",
"details": {
"reason": "signature"
}
}
}
Disponibilidade
Quando MYRIA_API_V1_ENABLED=false, endpoints v1 retornam 404 api_v1_disabled.
Quando MYRIA_API_V1_EXECUTIONS_ENABLED=false, endpoints de execução retornam
404 api_v1_executions_disabled.
Códigos comuns:
| HTTP | Código | Quando ocorre |
|---|---|---|
400 | invalid_json | JSON malformado. |
400 | invalid_payload | Payload fora do formato esperado. |
400 | invalid_template | Template de criação de estudo desconhecido. |
400 | invalid_mode | Modo incremental diferente de rv ou m. |
400 | invalid_count | Quantidade incremental fora de 1..24. |
400 | empty_study | Estudo não tem rodadas principais para servir de base incremental. |
400 | invalid_zip | Arquivo não é um ZIP válido, inclui entradas corrompidas ou ZIP aninhado inválido. |
400 | missing_file | Upload sem campo file. |
400 | invalid_execution_options | Opção de execução desconhecida, valor inválido ou decomp_cpu_cores sem capacidade conhecida ou acima da maior capacidade conhecida. |
400 | unknown_rodada | A rodada informada nao existe, nao foi importada ou nao pode ser resolvida pelo identificador enviado. |
400 | invalid_output | Output solicitado existe, mas não é JSON válido no formato esperado. |
409 | ambiguous_rodada | O nome informado para a rodada bate em mais de uma rodada possivel. |
409 | study_conflict | O estudo esta compartilhado ou em mutacao ativa. |
400/403/404 | unsupported_rodada_option | A opcao pedida nao se aplica ao tipo de rodada, ownership ou binario informado. |
400 | invalid_idempotency_key | Idempotency-Key inválida. |
401 | missing_api_key | Header Authorization ausente. |
401 | invalid_api_key | Chave malformada ou segredo inválido. |
401 | expired_api_key | Chave expirada. |
401 | revoked_api_key | Chave revogada. |
403 | insufficient_scope | Chave sem escopo exigido. |
403 | forbidden | Chave válida sem permissão para o recurso da organização. |
403 | organization_mismatch | org_slug diferente da organização da chave. |
405 | method_not_allowed | Método HTTP fora do contrato do endpoint. |
404 | not_found | Recurso inexistente ou de outra organização. |
404 | api_v1_disabled | API v1 desabilitada no ambiente. |
404 | api_v1_executions_disabled | Execuções via API v1 desabilitadas no ambiente. |
404 | output_not_found | Output solicitado não existe no path materializado da rodada ou o run_id atual da rodada não corresponde ao snapshot da execução. |
409 | idempotency_conflict | Mesma chave idempotente com payload ou arquivo diferente. |
409 | unsupported_execution_mode | Execução parcial, reexecução, resume, reset ou prioridade explícita não suportada. |
409 | study_not_ready | Estudo ainda não está pronto para execução. |
409 | active_execution | Já existe execução ativa conflitante. |
409 | no_active_server | Organização não tem servidor ativo para executar. |
409 | study_mutation_conflict | Estudo tem importação, validação ou mutação incremental em andamento. |
409 | active_incremental_operation | Já existe operação incremental em andamento para o estudo. |
409 | shared_deck | Recurso é deck compartilhado, não estudo operacional. |
409 | shared_deck_not_executable | Recurso é deck compartilhado, não estudo operacional. |
409 | pre_dispatch_failed | Configuração impede o despacho, por exemplo binário ausente. |
409 | output_not_ready | Output solicitado antes de a execução chegar a status terminal. |
413 | file_too_large | ZIP excede limite permitido. |
413 | output_too_large | Output JSON excede o limite operacional de retorno inline. |
415 | unsupported_media_type | Corpo não vazio enviado com Content-Type fora de JSON/form/multipart. |
429 | rate_limited | Limite de chamadas excedido. |
500 | study_creation_failed | Falha inesperada ao criar estudo Fast Track. |
500 | output_read_failed | Falha operacional ao ler output no Gitea. |
500 | incremental_operation_failed | Falha inesperada antes de a operação incremental virar recurso consultável. Falhas depois da persistência retornam o próprio recurso com status: "failed". |
503 | dispatch_failed | Falha ao enfileirar processamento. |
503 | fast_track_unavailable | Falha ao descobrir a base Fast Track sem criar estudo parcial. error.details.reason usa código público estável e não expõe paths ou mensagens internas do backend. |
Boas práticas
- Use
Idempotency-Keyem todoPOSTe no PATCH de opções de rodada. - Separe chaves por uso: uma para importação e outra para execução, se possível.
- Guarde a chave em variável de ambiente, nunca no código.
- Trate
429respeitando o cabeçalhoRetry-After. - Consulte o status por polling com intervalo de alguns segundos; evite loops sem espera.
- Não reenvie o ZIP com outra idempotency key antes de verificar o status da importação original.