> ## Documentation Index
> Fetch the complete documentation index at: https://muveya.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Exportações do resumo gerencial

> Peça uma exportação CSV assíncrona do resumo gerencial, acompanhe o status até ficar pronta, baixe o arquivo com um link de curta duração e verifique o checksum.

Uma exportação do resumo gerencial é um arquivo CSV com o mesmo resumo que `GET /v1/analytics/briefing` retorna: alertas, exceções de entrega, aprovações pendentes, consumo e tempos de ciclo. O muveya gera o arquivo em segundo plano, então o fluxo tem três passos: pedir, acompanhar e baixar.

`POST /v1/analytics/exports` é a única operação de `/v1` que grava algo, e o que ela grava é o próprio trabalho de exportação. Nada muda na sua operação.

## Quem pode fazer

Uma chave com o escopo `analytics:read` (veja [Escopos](/docs/pt/api-reference/scopes)). O mesmo escopo cobre pedir, acompanhar e baixar.

## O fluxo

```mermaid theme={null}
sequenceDiagram
  participant App as Seu servidor
  participant API as api.muveya.com
  App->>API: POST /v1/analytics/exports
  API-->>App: 202, status pending, exportId
  loop a cada poucos segundos
    App->>API: GET /v1/analytics/exports/exportId
    API-->>App: 200, status pending ou building
  end
  API-->>App: 200, status ready, downloadUrl, checksum
  App->>App: baixa o arquivo e verifica o SHA-256
```

## Passo 1: peça a exportação

<ParamField body="period" type="string" default="daily">
  A periodicidade do resumo: `daily` ou `weekly`. Qualquer outro valor é recusado com `400` e o código `common.invalid_request`. O corpo é opcional; sem ele você recebe um resumo diário. Não envie nenhum outro campo.
</ParamField>

<ParamField header="Accept-Language" type="string" default="en">
  O idioma da coluna `label` do CSV: `en`, `es` ou `pt`. Ele fica definido no momento da solicitação, porque o arquivo é gerado depois. As chaves de métricas, dimensões, unidades, valores e ids de evidência são iguais em todos os idiomas.
</ParamField>

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.muveya.com/v1/analytics/exports \
    -H "Authorization: Bearer $MUVEYA_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Accept-Language: es" \
    -d '{"period": "weekly"}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.muveya.com/v1/analytics/exports", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MUVEYA_API_KEY}`,
      "Content-Type": "application/json",
      "Accept-Language": "es",
    },
    body: JSON.stringify({ period: "weekly" }),
  });
  const job = await response.json();
  console.log(response.status, job.exportId, job.status);
  ```

  ```python Python theme={null}
  import os
  import requests

  response = requests.post(
      "https://api.muveya.com/v1/analytics/exports",
      headers={
          "Authorization": f"Bearer {os.environ['MUVEYA_API_KEY']}",
          "Accept-Language": "es",
      },
      json={"period": "weekly"},
      timeout=30,
  )
  job = response.json()
  print(response.status_code, job["exportId"], job["status"])
  ```
</CodeGroup>

```json 202 Accepted theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "pending",
  "reportType": "briefing",
  "period": "weekly"
}
```

Guarde o `exportId`: ele é a única referência ao trabalho.

<Note>
  Esta operação não tem chave de idempotência. Cada `POST` cria um trabalho de exportação novo e independente. Se uma requisição expirar antes de você receber o `exportId`, envie-a de novo; o trabalho extra não causa problemas e é excluído junto com os demais após 7 dias.
</Note>

## Passo 2: acompanhe até ficar pronta

Chame `GET /v1/analytics/exports/{exportId}` a cada poucos segundos (por exemplo, a cada 3 segundos). Cada consulta conta para o seu [limite de uso](/docs/pt/api-reference/rate-limits).

| `status` | Significado | O que fazer |
| - | - | - |
| `pending` | O trabalho foi aceito e aguarda a geração. | Continue consultando. |
| `building` | O arquivo está sendo montado. | Continue consultando. |
| `ready` | O arquivo está armazenado. A resposta agora inclui `byteSize`, `checksum`, `downloadUrl` e `downloadExpiresAt`. | Baixe o arquivo (passo 3). |
| `failed` | Não foi possível gerar o arquivo. A resposta inclui `error`. | Veja [Quando o trabalho falha](#quando-o-trabalho-falha). |

```json 200 OK (building) theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "building",
  "reportType": "briefing",
  "period": "weekly"
}
```

```json 200 OK (ready) theme={null}
{
  "exportId": "3f6c2a9e-0b1d-4c7a-9e55-1a2b3c4d5e6f",
  "status": "ready",
  "reportType": "briefing",
  "period": "weekly",
  "byteSize": 1874,
  "checksum": "5d41c0e8b7a24f3e9c6b1d0a8f7e6d5c4b3a29180f1e2d3c4b5a69788796a5b4",
  "downloadUrl": "https://files.example.invalid/management-briefing-weekly.csv?signature=EXAMPLE",
  "downloadExpiresAt": "2026-09-17T14:05:00.000Z"
}
```

<ResponseField name="exportId" type="string" required>
  O id do trabalho que você recebeu ao solicitar a exportação.
</ResponseField>

<ResponseField name="status" type="string" required>
  `pending`, `building`, `ready` ou `failed`.
</ResponseField>

<ResponseField name="reportType" type="string" required>
  O relatório contido no arquivo. Hoje é sempre `briefing`.
</ResponseField>

<ResponseField name="period" type="string" required>
  `daily` ou `weekly`, conforme a solicitação.
</ResponseField>

<ResponseField name="byteSize" type="integer">
  O tamanho do arquivo em bytes. Aparece quando o status é `ready`.
</ResponseField>

<ResponseField name="checksum" type="string">
  O SHA-256 do arquivo, em 64 caracteres hexadecimais minúsculos. Aparece quando o status é `ready`.
</ResponseField>

<ResponseField name="downloadUrl" type="string">
  Um link de download assinado. Aparece somente quando o status é `ready`. Cada consulta emite um link novo.
</ResponseField>

<ResponseField name="downloadExpiresAt" type="string">
  Quando o `downloadUrl` atual deixa de funcionar (UTC).
</ResponseField>

<ResponseField name="error" type="string">
  Um motivo legível por máquina. Aparece quando o status é `failed`. Um trabalho que falhou uma vez e foi repetido automaticamente mantém este campo enquanto está em `building` e depois de chegar a `ready`, então verifique sempre `status` primeiro.
</ResponseField>

## Passo 3: baixe e verifique

* O `downloadUrl` funciona por **5 minutos** depois da consulta que o retornou. Ele tem a própria assinatura: não adicione sua chave de API nem outro cabeçalho à requisição de download.
* Baixe imediatamente. Não guarde nem compartilhe o link: enquanto ele for válido, qualquer pessoa que o tenha pode baixar o arquivo. Se ele expirou, consulte o trabalho de novo para obter um link novo.
* O arquivo se chama `management-briefing-daily.csv` ou `management-briefing-weekly.csv`.
* Compare o SHA-256 dos bytes recebidos com `checksum`, e o tamanho com `byteSize`. Se forem diferentes, baixe de novo.

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -o management-briefing-weekly.csv "$DOWNLOAD_URL"
  sha256sum management-briefing-weekly.csv
  # The first value must equal the checksum field
  ```

  ```javascript JavaScript theme={null}
  import { createHash } from "node:crypto";
  import { writeFile } from "node:fs/promises";

  const file = Buffer.from(await (await fetch(job.downloadUrl)).arrayBuffer());
  const sha256 = createHash("sha256").update(file).digest("hex");
  if (sha256 !== job.checksum || file.length !== job.byteSize) {
    throw new Error("Export download is incomplete; download it again");
  }
  await writeFile("management-briefing-weekly.csv", file);
  ```

  ```python Python theme={null}
  import hashlib
  import requests

  file = requests.get(job["downloadUrl"], timeout=60).content
  if hashlib.sha256(file).hexdigest() != job["checksum"] or len(file) != job["byteSize"]:
      raise RuntimeError("Export download is incomplete; download it again")
  with open("management-briefing-weekly.csv", "wb") as handle:
      handle.write(file)
  ```
</CodeGroup>

## Exemplo completo

<CodeGroup>
  ```javascript JavaScript theme={null}
  import { createHash } from "node:crypto";
  import { writeFile } from "node:fs/promises";

  const BASE_URL = "https://api.muveya.com";
  const auth = { Authorization: `Bearer ${process.env.MUVEYA_API_KEY}` };
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

  async function exportBriefing(period = "weekly", language = "en") {
    const created = await fetch(`${BASE_URL}/v1/analytics/exports`, {
      method: "POST",
      headers: { ...auth, "Content-Type": "application/json", "Accept-Language": language },
      body: JSON.stringify({ period }),
    });
    if (created.status !== 202) throw new Error(`Export request failed: ${created.status}`);
    let job = await created.json();

    for (let attempt = 0; attempt < 100 && job.status !== "ready"; attempt += 1) {
      if (job.status === "failed") throw new Error(`Export failed: ${job.error}`);
      await sleep(3000);
      const polled = await fetch(`${BASE_URL}/v1/analytics/exports/${job.exportId}`, { headers: auth });
      if (polled.status === 429) {
        await sleep(Number(polled.headers.get("retry-after") ?? "1") * 1000);
        continue;
      }
      if (!polled.ok) throw new Error(`Export poll failed: ${polled.status}`);
      job = await polled.json();
    }
    if (job.status !== "ready") throw new Error("Export is taking too long; poll it again later");

    const file = Buffer.from(await (await fetch(job.downloadUrl)).arrayBuffer());
    if (createHash("sha256").update(file).digest("hex") !== job.checksum) {
      throw new Error("Checksum mismatch; poll again for a fresh link and retry");
    }
    await writeFile(`management-briefing-${period}.csv`, file);
    return job;
  }

  console.log(await exportBriefing("weekly", "es"));
  ```

  ```python Python theme={null}
  import hashlib
  import os
  import time
  import requests

  BASE_URL = "https://api.muveya.com"
  session = requests.Session()
  session.headers["Authorization"] = f"Bearer {os.environ['MUVEYA_API_KEY']}"

  def export_briefing(period="weekly", language="en"):
      created = session.post(
          f"{BASE_URL}/v1/analytics/exports",
          json={"period": period},
          headers={"Accept-Language": language},
          timeout=30,
      )
      if created.status_code != 202:
          raise RuntimeError(f"Export request failed: {created.status_code}")
      job = created.json()

      for _ in range(100):
          if job["status"] == "ready":
              break
          if job["status"] == "failed":
              raise RuntimeError(f"Export failed: {job['error']}")
          time.sleep(3)
          polled = session.get(f"{BASE_URL}/v1/analytics/exports/{job['exportId']}", timeout=30)
          if polled.status_code == 429:
              time.sleep(int(polled.headers.get("Retry-After", "1")))
              continue
          polled.raise_for_status()
          job = polled.json()
      else:
          raise RuntimeError("Export is taking too long; poll it again later")

      file = requests.get(job["downloadUrl"], timeout=60).content
      if hashlib.sha256(file).hexdigest() != job["checksum"]:
          raise RuntimeError("Checksum mismatch; poll again for a fresh link and retry")
      with open(f"management-briefing-{period}.csv", "wb") as handle:
          handle.write(file)
      return job

  print(export_briefing("weekly", "es"))
  ```
</CodeGroup>

## O arquivo CSV

* Codificação UTF-8 com marca de ordem de bytes, para que as planilhas mostrem os acentos corretamente. As linhas terminam em CRLF.
* Uma linha de cabeçalho e depois uma linha por número do resumo.
* Células que contêm vírgula, aspas ou quebra de linha ficam entre aspas. Uma célula que começa com `=`, `+`, `-`, `@`, tabulação ou retorno de carro recebe uma aspa simples (`'`) na frente, para que uma planilha nunca a execute como fórmula.

| Coluna | Conteúdo |
| - | - |
| `metric` | A métrica de origem, por exemplo `LOW_STOCK`, `APPROVAL_BACKLOG`, `CONSUMPTION_TREND` ou `ORDER_CYCLE_TIME`. |
| `dimension` | A parte de uma métrica com várias linhas: uma faixa de tempo de espera, uma etapa do ciclo ou uma série de consumo. Vazia nas métricas de valor único. |
| `label` | O rótulo para pessoas, no idioma que você pediu. |
| `value` | O número. **Vazio** quando a métrica não teve amostra, nunca `0`. |
| `unit` | A unidade de `value`: `count`, `orders`, `movements`, `hours` ou a unidade de medida de um insumo (`box`, `unit` etc.). |
| `evidenceId` | A referência reproduzível da métrica de origem, a mesma que as operações de análise retornam. |

```csv management-briefing-weekly.csv (trecho, Accept-Language: pt) theme={null}
metric,dimension,label,value,unit,evidenceId
LOW_STOCK,,Alertas abaixo do mínimo,3,count,LOW_STOCK
EXPIRING_STOCK,,Alertas de caixas perto do vencimento,1,count,EXPIRING_STOCK
PENDING_ORDERS,,Aprovações pendentes,2,count,PENDING_ORDERS
APPROVAL_BACKLOG,within_24h,Aprovações pendentes: menos de 24 horas,2,orders,APPROVAL_BACKLOG
CONSUMPTION_TREND,movements,Movimentos de consumo,48,movements,CONSUMPTION_TREND:granularity=week
CONSUMPTION_TREND,66d0a1f0c0ffee0000000401:box,"Nitrile gloves, size M",14,box,CONSUMPTION_TREND:granularity=week
ORDER_CYCLE_TIME,submit_to_dispatch,Tempo de ciclo: da solicitação ao despacho,26.5,hours,ORDER_CYCLE_TIME
ORDER_CYCLE_TIME,receive_to_close,Tempo de ciclo: do recebimento ao fechamento,,hours,ORDER_CYCLE_TIME
```

Os números são os mesmos que `GET /v1/analytics/briefing` retorna para o mesmo período no momento em que o arquivo foi gerado. O nome de cada insumo aparece como está no seu catálogo. Para saber o que cada métrica significa, veja [Análises gerenciais](/docs/pt/reports/management-analytics).

## O que o arquivo nunca contém

A exportação omite dados sensíveis por construção: ela contém contagens, quantidades, durações, rótulos e ids de evidência. Nunca contém custos, valores de pedidos, referências de pacientes nem nomes de membros da equipe. Ela cobre todas as unidades e depósitos da clínica odontológica, como qualquer outra leitura com chave de API.

## O que o muveya registra

* Cada solicitação de exportação fica na auditoria da sua clínica odontológica, com a chave de API como autora, o tipo de relatório e o período.
* Quando o arquivo é gerado, a auditoria também registra o tamanho e o checksum SHA-256, para que depois seja possível comparar o arquivo que você tem com o registro.
* Os trabalhos de exportação são mantidos por **7 dias** a partir da solicitação. Depois disso, consultar o trabalho retorna `404` com o código `common.not_found`: peça uma nova exportação.

## Quando o trabalho falha

| `error` | Significado | O que fazer |
| - | - | - |
| `schedule_failed` | Não foi possível colocar o trabalho na fila de geração. Você nunca consulta um trabalho assim: o próprio `POST` falha com `500` (`common.internal_error`) e não devolve `exportId`. | Peça uma nova exportação. |
| `processing_error` | A geração do arquivo falhou. O muveya tenta de novo automaticamente, então o trabalho pode voltar para `building` e terminar em `ready`. | Continue consultando por alguns minutos. Se continuar em `failed`, peça uma nova exportação. |

Se as novas exportações continuarem falhando, escreva para [team@muveya.com](mailto:team@muveya.com) com o `exportId`.

## Erros

| Status | `code` | Causa |
| - | - | - |
| `400` | `common.invalid_request` | `period` não é `daily` nem `weekly`, ou o corpo não é JSON válido |
| `401` | `api_keys.invalid` | Veja [Autenticação](/docs/pt/api-reference/authentication#respostas-401-e-403) |
| `403` | `api_keys.scope_missing` | A chave não tem `analytics:read` |
| `404` | `common.not_found` | Consulta de um `exportId` desconhecido, de uma exportação de outra clínica odontológica ou de uma com mais de 7 dias |
| `429` | `common.too_many_requests` | Veja [Limites de uso](/docs/pt/api-reference/rate-limits) |
| `500` | `common.internal_error` | Não foi possível colocar a exportação na fila. Peça uma nova exportação |

## Páginas relacionadas

* [Análises gerenciais](/docs/pt/reports/management-analytics)
* [Escopos](/docs/pt/api-reference/scopes)
* [Erros](/docs/pt/api-reference/errors)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.