Pular para o conteúdo principal

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-24.

Como conseguir uma chave

No Portal, acesse Perfil → Chaves de API.

Crie uma chave com os escopos necessários:

EscopoPermite
studies:writeCriar estudos Fast Track, enviar ZIPs, criar importações, adicionar RV+/M+ e alterar opções de rodadas.
studies:readConsultar importações e estudos.
executions:writeSolicitar execuções.
executions:readConsultar 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:

BucketCriação de estudosUploadsOperações incrementaisExecuções
Chave30/hour30/hour60/hour60/hour
Organização300/hour300/hour600/hour600/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.

Os buckets usam env vars no formato <numero>/<janela>. Os nomes são MYRIA_API_V1_RATE_LIMIT_KEY_STUDIES, MYRIA_API_V1_RATE_LIMIT_ORG_STUDIES, MYRIA_API_V1_RATE_LIMIT_KEY_UPLOADS, MYRIA_API_V1_RATE_LIMIT_ORG_UPLOADS, MYRIA_API_V1_RATE_LIMIT_KEY_INCREMENTALS, MYRIA_API_V1_RATE_LIMIT_ORG_INCREMENTALS, MYRIA_API_V1_RATE_LIMIT_KEY_EXECUTIONS e MYRIA_API_V1_RATE_LIMIT_ORG_EXECUTIONS.

Fluxo recomendado

  1. Para Novo Estudo operacional, chame POST /api/v1/studies com template: "fast_track". Para importar deck próprio, envie o ZIP em POST /api/v1/studies/imports.
  2. Na criação Fast Track, guarde o creation_id e o study_id. Na importação ZIP, guarde o import_id.
  3. Se importou ZIP, consulte GET /api/v1/studies/imports/{import_id} até receber um study_id.
  4. Consulte GET /api/v1/studies/{study_id} até ready_for_execution ser true.
  5. Para adicionar rodadas, chame POST /api/v1/studies/{study_id}/incremental-operations com mode: "rv" ou mode: "m" e consulte GET /api/v1/studies/{study_id}/incremental-operations/{operation_id}.
  6. Dispare POST /api/v1/studies/{study_id}/executions.
  7. Consulte GET /api/v1/studies/{study_id}/executions/{execution_id} até a execução terminar.

Receita ponta a ponta sem usar a UI

  1. Crie o Fast Track com POST /api/v1/studies, escopo studies:write, Content-Type: application/json e body {"template": "fast_track"}. Use Idempotency-Key e guarde creation_id, study_id e study_status_url.
  2. Consulte GET /api/v1/studies/{study_id} com studies:read até ready_for_execution ficar true ou o status chegar a blocked/failed.
  3. Adicione RV+ com POST /api/v1/studies/{study_id}/incremental-operations e body {"mode": "rv", "count": 1}. Adicione M+ com {"mode": "m"}. O count pode ir de 1 a 24; RV+ na última RV do mês pode cruzar para M+ quando a regra operacional pedir isso.
  4. Aguarde a preparação em GET /api/v1/studies/{study_id}/incremental-operations/{operation_id}. Em completed, o campo rodadas traz as rodadas criadas no mesmo formato de GET /api/v1/studies/{study_id}.
  5. Ajuste corte, PREVS, binários e GEVAZP com o PATCH existente PATCH /api/v1/studies/{study_id}/rodadas/{rodada_id_or_name}/options. Ele aceita corte, prevs_file, prevs_source, decomp_base, decomp_base_earm_column, binario_id, gevazp.binario_id e gevazp.run_gevazp.
  6. Execute o estudo inteiro com POST /api/v1/studies/{study_id}/executions. Execução parcial, resume, reset, reexecução e prioridade explícita ainda retornam 409 unsupported_execution_mode.
  7. Consulte GET /api/v1/studies/{study_id}/executions/{execution_id} até status terminal. Use requested_rodadas e run_id_snapshot para saber quais rodadas pertencem à execução.
  8. 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 o run_id atual bate com o run_id registrado 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:

CampoTipoObrigatórioDescrição
templatetextosimNesta versão, apenas "fast_track".
titletextonãoTí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_slugtextonãoSe 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:

CampoTipoObrigatórioDescrição
filearquivo .zipsimZIP do estudo.
titletextonãoTítulo sugerido para o estudo.
titulotextonãoAlias aceito para title.
org_slugtextonãoSe enviado, deve ser igual à organização da chave.
selected_rodadaslista, JSON ou textonãoSubconjunto de rodadas a importar. Aceita campo repetido, selected_rodadas[], array JSON string ou lista separada por vírgulas.
execution_optionsJSON objectnãoReservado para opções futuras. Campos internos ainda não são aceitos.
rodada_optionsJSON objectnãoOpcoes 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:

StatusSignificado
uploadedZIP recebido e aguardando processamento.
parsingZIP em leitura/normalização.
materializingArquivos sendo gravados no Gitea.
validatingReadiness sendo calculado.
readyEstudo pronto para execução.
blockedEstudo criado com pendências bloqueantes.
failedFalha 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:

StatusSignificado
materializingArquivos ainda estão sendo gravados.
validatingValidação/readiness ainda está em andamento.
readyEstudo pronto para execução.
blockedHá pendências bloqueantes.
failedHouve 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:

CampoTipoObrigatórioDescrição
modetextosim"rv" para RV+ ou "m" para M+.
countinteironãoQuantidade 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:

StatusSignificado
pendingOperação aceita e aguardando preparação no worker.
runningRodadas incrementais foram criadas e estão em montagem/materialização.
completedPreparação e montagem terminaram.
failedPreparação, dispatch ou montagem falhou; consulte error.
cancelledOperaçã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:

  • corte
  • prevs_file
  • prevs_source
  • decomp_base
  • decomp_base_earm_column
  • binario_id
  • gevazp.binario_id
  • gevazp.run_gevazp

prevs_file deve ser apenas o nome do arquivo PREVS, sem caminho e sem metacaracteres de glob. Formatos aceitos incluem prevs.rv0, prevs-2000.rv0 e variantes seguras com letras, números, _, - ou . antes do sufixo .rvN.

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_base so se aplica a rodadas decomp.
  • decomp_base_earm_column aceita apenas "inic" ou null; campo ausente preserva a regra operacional atual.
  • O deck fonte precisa conter sumario.* ou relato.* com EARM por usina.
  • Se uma UHE do dadger alvo nao existir na fonte, o valor atual dessa linha e mantido.
  • Regras de preflight (preflight_rules ou regras_volume_uhe.dat) rodam depois desse ajuste e podem sobrescrever volumes UH especificos.

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, readiness suficiente para iniciar a cadeia e capacidade operacional: ao menos um servidor ativo na organização ou, no rollout OCI atual, overflow OCI elegível para a organização: limite OCI da organização com max_dynamic_ocpus > 0, decomp_ocpus/newave_ocpus válidos, credenciais OCI globais completas e descriptografáveis em ConfigGeral, shapes e memória OCI globais válidos, ao menos um placement regional global habilitado e válido, MYRIA_OCI_DYNAMIC_ENABLED=true e MYRIA_OCI_PROVIDER_DRY_RUN=false. Um placement regional global válido inclui região, compartment, availability domain, subnet, imagem, usuário/chave SSH e NSG quando houver IP público. 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:

CampoTipoDescrição
org_slugtextoOpcional. Se enviado, deve ser igual à organização da chave; divergência retorna 403 organization_mismatch.
execution_options.decomp_cpu_coresinteiro ou nullCores a reservar para cada rodada DECOMP. Alias: processors. Valor explícito exige capacidade fixa conhecida ou overflow OCI elegível para a organização; o limite validado é o maior valor entre a capacidade fixa conhecida e o decomp_ocpus da organização. Se a execução cair em OCI dinâmica, o MVP usa o decomp_ocpus da organização e ignora esse override por estudo.
execution_options.decomp_max_inviab_attemptsinteiro 1..20 ou nullMáximo de execuções internas do tratamento de inviabilidades DECOMP. Alias: infeasibility_treatment_attempts.
rodada_optionsobjetoOpcoes 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",
"output": {
"name": "deckoutput.json",
"gitea_repo": "estudos",
"gitea_path": "estudo_42/decomp_rv0/deckoutput.json",
"ref": "main"
}
}
},
"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 tiver sido criada, mas ainda não tiver enfileirado rodadas, uma nova chamada com o mesmo Idempotency-Key tenta retomar o start antes de retornar o replay. Se as rodadas já tiverem sido enfileiradas, mas o snapshot da execução ainda não tiver sido gravado, o replay reconstrói requested_rodadas e run_id_snapshot a partir das rodadas atuais e retorna o recurso existente. Se o start falhar antes de enfileirar, 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âmetroDefaultDescrição
limit50Quantidade máxima de execuções retornadas. Aceita 1..100.
offset0Quantidade 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:

StatusSignificado
queuedExecução solicitada e aguardando servidor ou worker.
runningPelo menos uma rodada está ativa.
completedTodas as rodadas terminaram com sucesso.
nonconvergedRodadas terminaram sem convergência ou com mistura de sucesso e não convergência.
failedFalha operacional.
stoppedExecução parada.
partialMistura 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. A leitura exige run_id e snapshot de output gravados na execução, para não usar o path atual de uma rodada reexecutada ou editada; o ref do snapshot usa o commit de upload quando disponível, ou main enquanto o commit final ainda não foi registrado. 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": {},
"carga": {
"SE": {
"estagios": {
"1": {
"pesado": {"carga": 52409.0, "horas": 30.0},
"medio": {"carga": 52370.0, "horas": 74.0},
"leve": {"carga": 43304.0, "horas": 64.0},
"media_sub": {"carga": 48923.0, "horas": 168.0}
}
}
}
},
"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,
"Mes_01": 25950.00,
"Mes_01_perc_MLT": 86.50
},
"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,
"Mes_01": 7530.00,
"Mes_01_perc_MLT": 75.30
},
"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,
"Mes_01": 3790.00,
"Mes_01_perc_MLT": 75.80
},
"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,
"Mes_01": 7000.00,
"Mes_01_perc_MLT": 87.50
}
}
}
}
}

Regras principais:

  • carga vem dos registros DP do DADGER, agrupada pelos submercados SE, S, NE e N, por estagio e patamar (pesado, medio, leve);
  • cada patamar contem carga e horas; valores numericos ausentes sao retornados como null, sem incluir comentarios do DADGER;
  • media_sub.horas soma as horas de pesado, medio e leve, enquanto media_sub.carga calcula a media das cargas ponderada por essas horas e a arredonda para o inteiro mais proximo, mantendo representacao decimal (.0);
  • se algum patamar nao tiver carga ou horas, media_sub.carga retorna null e media_sub.horas continua somando as horas disponiveis;
  • se essa leitura falhar, carga retorna {} e diagnosticos.carga informa o motivo sem remover os demais dados;
  • em rodadas DECOMP, ena e calculada a partir de prevs.rvX, REGRAS.DAT, hidr.dat e da produtividade do estagio 1 de relato.rvX;
  • a ENA e agregada por submercado e sempre usa Sem_01 a Sem_06, independentemente da revisao;
  • Mes_01 representa a media mensal da ENA do mes da rodada, ponderando os dias daquele mes presentes em cada uma das seis semanas;
  • Mes_01_perc_MLT representa Mes_01 dividido pela media mensal de longo termo de org-myria/arquivos/ena/mlt_ena.json, multiplicado por 100 e arredondado para 2 casas decimais;
  • se a tabela de MLT estiver ausente, incompleta ou invalida, a chave Mes_01_perc_MLT fica ausente para os submercados afetados e diagnosticos.ena_mlt informa o motivo;
  • se o calculo de ENA falhar, o restante do deckoutput.json continua disponivel, ena retorna {} e diagnosticos.ena informa o motivo;
  • a execução precisa estar em status terminal (completed, nonconverged, failed, stopped ou partial);
  • a rodada precisa pertencer ao snapshot da execução;
  • se o snapshot da execução tem run_id, ele precisa corresponder ao run_id atual 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_id da execução, a API retorna 404 output_not_found.

Exemplos em Python

Os exemplos usam requests e podem ser colados em um arquivo local. Configure MYRIA_URL com a URL do Portal e MYRIA_API_KEY com a chave criada no perfil.

pip install requests
export MYRIA_URL="https://portal.exemplo.com"
export MYRIA_API_KEY="myria_live_<identificador>.<segredo>"

Cliente pronto

import json
import os
import time
import uuid
from pathlib import Path
from typing import Any

import requests


TERMINAL_EXECUTION_STATUSES = {
"completed",
"nonconverged",
"failed",
"stopped",
"partial",
}


class MyriaApiError(RuntimeError):
def __init__(
self,
status_code: int,
code: str,
message: str,
*,
details: dict[str, Any] | None = None,
retry_after: str | None = None,
):
super().__init__(f"{status_code} {code}: {message}")
self.status_code = status_code
self.code = code
self.message = message
self.details = details or {}
self.retry_after = retry_after


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}"})

@classmethod
def from_env(cls) -> "MyriaClient":
return cls(os.environ["MYRIA_URL"], os.environ["MYRIA_API_KEY"])

def _request(self, method: str, path: str, **kwargs) -> dict[str, Any]:
response = self.session.request(
method,
f"{self.base_url}{path}",
timeout=kwargs.pop("timeout", 30),
**kwargs,
)
if response.ok:
return response.json()

try:
payload = response.json()
except ValueError:
response.raise_for_status()

error = payload.get("error", {})
raise MyriaApiError(
response.status_code,
error.get("code", "unknown_error"),
error.get("message", response.text),
details=error.get("details") or {},
retry_after=response.headers.get("Retry-After"),
)

def create_fast_track(
self,
*,
title: str | None = None,
org_slug: str | None = None,
idempotency_key: str,
) -> dict[str, Any]:
payload: dict[str, Any] = {"template": "fast_track"}
if title:
payload["title"] = title
if org_slug:
payload["org_slug"] = org_slug
return self._request(
"POST",
"/api/v1/studies",
headers={"Idempotency-Key": idempotency_key},
json=payload,
)

def import_zip(
self,
zip_path: str,
*,
title: str | None = None,
selected_rodadas: list[str] | None = None,
rodada_options: dict[str, Any] | None = None,
org_slug: str | None = None,
idempotency_key: str,
) -> dict[str, Any]:
data: list[tuple[str, str]] = []
if title:
data.append(("title", title))
if org_slug:
data.append(("org_slug", org_slug))
for rodada in selected_rodadas or []:
data.append(("selected_rodadas[]", rodada))
if rodada_options:
data.append(("rodada_options", json.dumps(rodada_options)))

path = Path(zip_path)
with path.open("rb") as file_obj:
return self._request(
"POST",
"/api/v1/studies/imports",
headers={"Idempotency-Key": idempotency_key},
files={"file": (path.name, file_obj, "application/zip")},
data=data,
timeout=60,
)

def get_import(self, import_id: str) -> dict[str, Any]:
return self._request("GET", f"/api/v1/studies/imports/{import_id}")

def get_study(self, study_id: int) -> dict[str, Any]:
return self._request("GET", f"/api/v1/studies/{study_id}")

def add_incremental(
self,
study_id: int,
*,
mode: str = "rv",
count: int = 1,
idempotency_key: str,
) -> dict[str, Any]:
return self._request(
"POST",
f"/api/v1/studies/{study_id}/incremental-operations",
headers={"Idempotency-Key": idempotency_key},
json={"mode": mode, "count": count},
)

def get_incremental(self, study_id: int, operation_id: str) -> dict[str, Any]:
return self._request(
"GET",
f"/api/v1/studies/{study_id}/incremental-operations/{operation_id}",
)

def patch_rodada_options(
self,
study_id: int,
rodada_id_or_name: str,
options: dict[str, Any],
*,
idempotency_key: str,
) -> dict[str, Any]:
return self._request(
"PATCH",
f"/api/v1/studies/{study_id}/rodadas/{rodada_id_or_name}/options",
headers={"Idempotency-Key": idempotency_key},
json=options,
)

def start_execution(
self,
study_id: int,
*,
execution_options: dict[str, Any] | None = None,
rodada_options: dict[str, Any] | None = None,
idempotency_key: str,
) -> dict[str, Any]:
payload: dict[str, Any] = {}
if execution_options:
payload["execution_options"] = execution_options
if rodada_options:
payload["rodada_options"] = rodada_options
return self._request(
"POST",
f"/api/v1/studies/{study_id}/executions",
headers={"Idempotency-Key": idempotency_key},
json=payload,
)

def list_executions(
self,
study_id: int,
*,
limit: int = 50,
offset: int = 0,
) -> dict[str, Any]:
return self._request(
"GET",
f"/api/v1/studies/{study_id}/executions",
params={"limit": limit, "offset": offset},
)

def get_execution(self, study_id: int, execution_id: str) -> dict[str, Any]:
return self._request(
"GET",
f"/api/v1/studies/{study_id}/executions/{execution_id}",
)

def get_deckoutput(
self,
study_id: int,
execution_id: str,
rodada_id_or_name: str,
) -> dict[str, Any]:
return self._request(
"GET",
"/api/v1/studies/"
f"{study_id}/executions/{execution_id}/rodadas/"
f"{rodada_id_or_name}/outputs/deckoutput.json",
)

def wait_import_finished(
self,
import_id: str,
*,
interval_seconds: int = 10,
timeout_seconds: int = 1800,
) -> dict[str, Any]:
deadline = time.monotonic() + timeout_seconds
while True:
job = self.get_import(import_id)
if job["status"] in {"ready", "blocked", "failed"}:
return job
if time.monotonic() > deadline:
raise TimeoutError(f"Importacao {import_id} nao terminou a tempo")
time.sleep(interval_seconds)

def wait_study_ready(
self,
study_id: int,
*,
interval_seconds: int = 10,
timeout_seconds: int = 1800,
) -> dict[str, Any]:
deadline = time.monotonic() + timeout_seconds
while True:
study = self.get_study(study_id)["study"]
if study["ready_for_execution"]:
return study
if study["status"] in {"blocked", "failed"}:
raise RuntimeError(study)
if time.monotonic() > deadline:
raise TimeoutError(f"Estudo {study_id} nao ficou pronto a tempo")
time.sleep(interval_seconds)

def wait_incremental_finished(
self,
study_id: int,
operation_id: str,
*,
interval_seconds: int = 10,
timeout_seconds: int = 1800,
) -> dict[str, Any]:
deadline = time.monotonic() + timeout_seconds
while True:
operation = self.get_incremental(study_id, operation_id)
if operation["status"] in {"completed", "failed", "cancelled"}:
return operation
if time.monotonic() > deadline:
raise TimeoutError(f"Operacao incremental {operation_id} nao terminou a tempo")
time.sleep(interval_seconds)

def wait_execution_terminal(
self,
study_id: int,
execution_id: str,
*,
interval_seconds: int = 15,
timeout_seconds: int = 7200,
) -> dict[str, Any]:
deadline = time.monotonic() + timeout_seconds
while True:
execution = self.get_execution(study_id, execution_id)
if execution["status"] in TERMINAL_EXECUTION_STATUSES:
return execution
if time.monotonic() > deadline:
raise TimeoutError(f"Execucao {execution_id} nao terminou a tempo")
time.sleep(interval_seconds)


def idem(prefix: str) -> str:
return f"{prefix}-{uuid.uuid4().hex}"

Criar Fast Track e aguardar readiness

client = MyriaClient.from_env()

created = client.create_fast_track(
title="Fast Track Junho 2026",
idempotency_key=idem("create-fast-track"),
)

study_id = created["study_id"]
study = client.wait_study_ready(study_id)

print("study_id:", study_id)
print("status:", study["status"])

Importar ZIP com opções de rodada

client = MyriaClient.from_env()

job = client.import_zip(
"decomp_202606_rv0.zip",
title="DECOMP PMO 202606 RV0",
selected_rodadas=["decomp_rv0"],
rodada_options={
"decomp_rv0": {
"prevs_file": "prevs.rv0",
"corte": {
"org_slug": "minha-org",
"gitea_repo": "estudos",
"gitea_path": "estudo_120/rodada_991",
},
}
},
idempotency_key=idem("import-decomp-rv0"),
)

job = client.wait_import_finished(job["import_id"])
if job["status"] == "failed":
raise RuntimeError(job["errors"])

study = client.wait_study_ready(job["study_id"])
print("study_id:", study["id"])

Adicionar RV+ e ajustar EARM de entrada

client = MyriaClient.from_env()
study_id = 42

operation = client.add_incremental(
study_id,
mode="rv",
count=1,
idempotency_key=idem("rv-plus"),
)
operation = client.wait_incremental_finished(study_id, operation["operation_id"])
if operation["status"] != "completed":
raise RuntimeError(operation["error"])

new_rodada = operation["rodadas"][0]["name"]

client.patch_rodada_options(
study_id,
new_rodada,
{
"decomp_base": {
"org_slug": "minha-org",
"gitea_repo": "estudos",
"gitea_path": "estudo_120/rodada_991",
},
"decomp_base_earm_column": "inic",
},
idempotency_key=idem("patch-earm-base"),
)

Executar o estudo e ler deckoutput.json

client = MyriaClient.from_env()
study_id = 42

execution = client.start_execution(
study_id,
execution_options={
"decomp_cpu_cores": 8,
"decomp_max_inviab_attempts": 7,
},
rodada_options={
"decomp_rv0": {
"prevs_file": "prevs.rv0",
}
},
idempotency_key=idem("exec-study"),
)

execution = client.wait_execution_terminal(study_id, execution["execution_id"])
print("execution status:", execution["status"])

if execution["errors"]:
print("execution errors:", execution["errors"])

for rodada in execution["rodadas"]:
print(rodada["name"], rodada["status"], rodada.get("run_id"))

completed_rodadas = [
rodada for rodada in execution["rodadas"]
if rodada["status"] in {"completed", "nonconverged"}
]
if completed_rodadas:
output = client.get_deckoutput(
study_id,
execution["execution_id"],
completed_rodadas[0]["name"],
)
print(output["output"]["content"].keys())

Listar execuções recentes

client = MyriaClient.from_env()
page = client.list_executions(42, limit=20)

for execution in page["executions"]:
print(execution["execution_id"], execution["status"], execution["created_at"])

if page["pagination"]["next_offset"] is not None:
next_page = client.list_executions(
42,
limit=20,
offset=page["pagination"]["next_offset"],
)

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:

HTTPCódigoQuando ocorre
400invalid_jsonJSON malformado.
400invalid_payloadPayload fora do formato esperado.
400invalid_templateTemplate de criação de estudo desconhecido.
400invalid_modeModo incremental diferente de rv ou m.
400invalid_countQuantidade incremental fora de 1..24.
400empty_studyEstudo não tem rodadas principais para servir de base incremental.
400invalid_zipArquivo não é um ZIP válido, inclui entradas corrompidas ou ZIP aninhado inválido.
400missing_fileUpload sem campo file.
400invalid_execution_optionsOpção de execução desconhecida, valor inválido ou decomp_cpu_cores sem capacidade conhecida ou acima da maior capacidade conhecida.
400unknown_rodadaA rodada informada nao existe, nao foi importada ou nao pode ser resolvida pelo identificador enviado.
400invalid_outputOutput solicitado existe, mas não é JSON válido no formato esperado.
409ambiguous_rodadaO nome informado para a rodada bate em mais de uma rodada possivel.
409study_conflictO estudo esta compartilhado ou em mutacao ativa.
400/403/404unsupported_rodada_optionA opcao pedida nao se aplica ao tipo de rodada, ownership ou binario informado.
400invalid_idempotency_keyIdempotency-Key inválida.
401missing_api_keyHeader Authorization ausente.
401invalid_api_keyChave malformada ou segredo inválido.
401expired_api_keyChave expirada.
401revoked_api_keyChave revogada.
403insufficient_scopeChave sem escopo exigido.
403forbiddenChave válida sem permissão para o recurso da organização.
403organization_mismatchorg_slug diferente da organização da chave.
405method_not_allowedMétodo HTTP fora do contrato do endpoint.
404not_foundRecurso inexistente ou de outra organização.
404api_v1_disabledAPI v1 desabilitada no ambiente.
404api_v1_executions_disabledExecuções via API v1 desabilitadas no ambiente.
404output_not_foundOutput solicitado não existe no path materializado da rodada ou o run_id atual da rodada não corresponde ao snapshot da execução.
409idempotency_conflictMesma chave idempotente com payload ou arquivo diferente.
409unsupported_execution_modeExecução parcial, reexecução, resume, reset ou prioridade explícita não suportada.
409study_not_readyEstudo ainda não está pronto para execução.
409active_executionJá existe execução ativa conflitante.
409no_active_serverOrganização não tem servidor ativo nem overflow OCI elegível.
409study_mutation_conflictEstudo tem importação, validação ou mutação incremental em andamento.
409active_incremental_operationJá existe operação incremental em andamento para o estudo.
409shared_deckRecurso é deck compartilhado, não estudo operacional.
409shared_deck_not_executableRecurso é deck compartilhado, não estudo operacional.
409pre_dispatch_failedConfiguração impede o despacho, por exemplo binário ausente.
409output_not_readyOutput solicitado antes de a execução chegar a status terminal.
413file_too_largeZIP excede limite permitido.
413output_too_largeOutput JSON excede o limite operacional de retorno inline.
415unsupported_media_typeCorpo não vazio enviado com Content-Type fora de JSON/form/multipart.
429rate_limitedLimite de chamadas excedido.
500study_creation_failedFalha inesperada ao criar estudo Fast Track.
500output_read_failedFalha operacional ao ler output no Gitea.
500incremental_operation_failedFalha inesperada antes de a operação incremental virar recurso consultável. Falhas depois da persistência retornam o próprio recurso com status: "failed".
503dispatch_failedFalha ao enfileirar processamento.
503fast_track_unavailableFalha 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-Key em todo POST e 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 429 respeitando o cabeçalho Retry-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.