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

# Guía rápida de la API

> Haz tus primeras llamadas a la API de muveya con cURL, JavaScript o Python: identifica tu clave, lista sedes e insumos del catálogo, lee un pedido y recorre páginas.

Esta página te lleva desde una clave de API nueva hasta una lista paginada de pedidos. Todos los ejemplos se ejecutan en tu servidor, nunca en un navegador.

## Antes de empezar

* Una clave de API con los scopes `clinics:read`, `catalog:read` y `orders:read`. Consulta [Autenticación](/docs/es/api-reference/authentication) para saber cómo obtenerla.
* Alguno de estos: `curl`, Node.js 18 o superior (incluye `fetch`), o Python 3 con el paquete `requests` (`pip install requests`).
* Los ejemplos de JavaScript usan `await` en el nivel superior: guárdalos en un archivo que termine en `.mjs`.

## Pasos

<Steps>
  <Step title="Pon la clave en una variable de entorno">
    Nunca escribas la clave en tu código. Expórtala en la terminal (o cárgala desde tu gestor de secretos):

    ```bash theme={null}
    export MUVEYA_API_KEY="mvy_test_EXAMPLE..."
    ```

    Todos los ejemplos de abajo leen `MUVEYA_API_KEY` y llaman a `https://api.muveya.com`.
  </Step>

  <Step title="Identifica la clave">
    `GET /v1/me` no requiere scope. Te dice qué clínica dental lee la clave y qué scopes tiene.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.muveya.com/v1/me \
        -H "Authorization: Bearer $MUVEYA_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const BASE_URL = "https://api.muveya.com";
      const headers = { Authorization: `Bearer ${process.env.MUVEYA_API_KEY}` };

      const response = await fetch(`${BASE_URL}/v1/me`, { headers });
      if (!response.ok) {
        const problem = await response.json();
        throw new Error(`${problem.status} ${problem.code} (request ${problem.requestId})`);
      }
      console.log(await response.json());
      ```

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

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

      response = session.get(f"{BASE_URL}/v1/me", timeout=30)
      if not response.ok:
          problem = response.json()
          raise RuntimeError(f"{problem['status']} {problem['code']} (request {problem['requestId']})")
      print(response.json())
      ```
    </CodeGroup>

    ```json Respuesta theme={null}
    {
      "workspaceId": "66d0a1f0c0ffee0000000001",
      "environment": "test",
      "scopes": ["clinics:read", "catalog:read", "orders:read"]
    }
    ```

    Un `401` con `api_keys.invalid` significa que el encabezado o la clave están mal. Consulta [Respuestas 401 y 403](/docs/es/api-reference/authentication#respuestas-401-y-403).
  </Step>

  <Step title="Lista tus sedes">
    `GET /v1/clinics` (scope `clinics:read`) devuelve todas las sedes de la clínica dental en una sola página. `GET /v1/warehouses` funciona igual para las bodegas.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.muveya.com/v1/clinics \
        -H "Authorization: Bearer $MUVEYA_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const clinics = await fetch(`${BASE_URL}/v1/clinics`, { headers }).then((r) => r.json());
      for (const clinic of clinics.data) {
        console.log(clinic.clinicId, clinic.name, clinic.status);
      }
      ```

      ```python Python theme={null}
      clinics = session.get(f"{BASE_URL}/v1/clinics", timeout=30).json()
      for clinic in clinics["data"]:
          print(clinic["clinicId"], clinic["name"], clinic["status"])
      ```
    </CodeGroup>

    ```json Respuesta theme={null}
    {
      "data": [
        { "clinicId": "66d0a1f0c0ffee0000000101", "name": "Clínica Norte", "status": "active" },
        { "clinicId": "66d0a1f0c0ffee0000000102", "name": "Clínica Sur", "status": "active" }
      ],
      "hasMore": false
    }
    ```

    Guarda una relación de `clinicId` a `name`: los pedidos y la analítica se refieren a las sedes por id.
  </Step>

  <Step title="Lista los insumos del catálogo">
    `GET /v1/catalog/items` (scope `catalog:read`) devuelve todos los insumos del catálogo, sin importar su `status`, en una sola página.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.muveya.com/v1/catalog/items \
        -H "Authorization: Bearer $MUVEYA_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const items = await fetch(`${BASE_URL}/v1/catalog/items`, { headers }).then((r) => r.json());
      const active = items.data.filter((item) => item.status === "active");
      console.log(`${active.length} active items`);
      ```

      ```python Python theme={null}
      items = session.get(f"{BASE_URL}/v1/catalog/items", timeout=30).json()
      active = [item for item in items["data"] if item["status"] == "active"]
      print(f"{len(active)} active items")
      ```
    </CodeGroup>

    ```json Respuesta theme={null}
    {
      "data": [
        {
          "itemId": "66d0a1f0c0ffee0000000401",
          "sku": "GLV-NIT-M",
          "name": "Nitrile gloves, size M",
          "categoryId": "66d0a1f0c0ffee0000000301",
          "unitOfMeasure": "box",
          "packaging": "Box of 100 gloves",
          "criticality": "high",
          "tracksLot": true,
          "tracksSerial": false,
          "tracksExpiry": true,
          "highValue": false,
          "status": "active"
        }
      ],
      "hasMore": false
    }
    ```

    Esta clave no tiene `catalog.cost:read`, así que los campos de costo no aparecen. Con ese scope, cada insumo también trae `costStatus` y, cuando hay un costo registrado, `cost`, `currency` y `costMeasurement`. Revisa `costStatus` antes de usar un importe: consulta [Costos](/docs/es/catalog/costs).
  </Step>

  <Step title="Lee un pedido">
    Toma un `orderId` de `GET /v1/orders` y léelo con `GET /v1/orders/{orderId}` (scope `orders:read`). El detalle incluye las líneas y el plan de aprobación.

    <CodeGroup>
      ```bash cURL theme={null}
      curl https://api.muveya.com/v1/orders/66d0a1f0c0ffee0000000501 \
        -H "Authorization: Bearer $MUVEYA_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      const orderId = "66d0a1f0c0ffee0000000501";
      const response = await fetch(`${BASE_URL}/v1/orders/${encodeURIComponent(orderId)}`, { headers });
      if (response.status === 404) {
        console.log("No such order in this dental clinic");
      } else {
        const order = await response.json();
        console.log(order.number, order.status, order.lines.length);
      }
      ```

      ```python Python theme={null}
      order_id = "66d0a1f0c0ffee0000000501"
      response = session.get(f"{BASE_URL}/v1/orders/{order_id}", timeout=30)
      if response.status_code == 404:
          print("No such order in this dental clinic")
      else:
          order = response.json()
          print(order["number"], order["status"], len(order["lines"]))
      ```
    </CodeGroup>

    ```json Respuesta theme={null}
    {
      "orderId": "66d0a1f0c0ffee0000000501",
      "number": 42,
      "type": "general",
      "status": "pending_approval",
      "requesterId": "66d0a1f0c0ffee0000000701",
      "clinicId": "66d0a1f0c0ffee0000000101",
      "destinationWarehouseId": "66d0a1f0c0ffee0000000202",
      "justification": "Weekly restock for the surgery rooms",
      "lines": [
        {
          "lineId": "66d0a1f0c0ffee0000000601",
          "catalogItemId": "66d0a1f0c0ffee0000000401",
          "sku": "GLV-NIT-M",
          "name": "Nitrile gloves, size M",
          "requestedQty": 12,
          "unitOfMeasure": "box",
          "category": "66d0a1f0c0ffee0000000301",
          "highValue": false
        }
      ],
      "approvalPlan": {
        "policyVersion": 3,
        "requiresApproval": true,
        "steps": [
          { "stage": "manager", "requiredScope": "approvals.decide", "minApprovers": 1 }
        ]
      },
      "version": 2
    }
    ```

    El `value` del pedido y el `patientRef` de un pedido clínico nunca se devuelven en `/v1`. Un pedido de otra clínica dental, o un id que no existe, responde `404` con el código `orders.not_found`.
  </Step>

  <Step title="Recorre las páginas">
    `GET /v1/orders` está paginado. Pide hasta 200 pedidos por página y devuelve `nextCursor` como `cursor` mientras `hasMore` sea `true`. Envía los mismos filtros en cada página.

    <CodeGroup>
      ```bash cURL theme={null}
      # First page
      curl "https://api.muveya.com/v1/orders?limit=2&status=approved" \
        -H "Authorization: Bearer $MUVEYA_API_KEY"

      # Next page: copy nextCursor from the previous response
      curl "https://api.muveya.com/v1/orders?limit=2&status=approved&cursor=MjAyNi0wOS0xMlQxNDowNTowMC4wMDBafDY2ZDBhMWYwYzBmZmVlMDAwMDAwMDQ5OQ" \
        -H "Authorization: Bearer $MUVEYA_API_KEY"
      ```

      ```javascript JavaScript theme={null}
      async function listAllOrders(status) {
        const orders = [];
        let cursor;
        do {
          const url = new URL("/v1/orders", BASE_URL);
          url.searchParams.set("limit", "200");
          if (status) url.searchParams.set("status", status);
          if (cursor) url.searchParams.set("cursor", cursor);

          const response = await fetch(url, { headers });
          if (!response.ok) throw new Error(`HTTP ${response.status}`);
          const page = await response.json();

          orders.push(...page.data);
          cursor = page.hasMore ? page.nextCursor : undefined;
        } while (cursor);
        return orders;
      }

      const approved = await listAllOrders("approved");
      console.log(`${approved.length} approved orders`);
      ```

      ```python Python theme={null}
      def list_all_orders(status=None):
          orders, cursor = [], None
          while True:
              params = {"limit": 200}
              if status:
                  params["status"] = status
              if cursor:
                  params["cursor"] = cursor
              response = session.get(f"{BASE_URL}/v1/orders", params=params, timeout=30)
              response.raise_for_status()
              page = response.json()
              orders.extend(page["data"])
              if not page["hasMore"]:
                  return orders
              cursor = page["nextCursor"]

      approved = list_all_orders("approved")
      print(f"{len(approved)} approved orders")
      ```
    </CodeGroup>

    ```json Primera página theme={null}
    {
      "data": [
        {
          "orderId": "66d0a1f0c0ffee0000000498",
          "number": 40,
          "type": "general",
          "status": "approved",
          "requesterId": "66d0a1f0c0ffee0000000701",
          "clinicId": "66d0a1f0c0ffee0000000101",
          "destinationWarehouseId": "66d0a1f0c0ffee0000000202",
          "version": 3
        },
        {
          "orderId": "66d0a1f0c0ffee0000000499",
          "number": 41,
          "type": "general",
          "status": "approved",
          "requesterId": "66d0a1f0c0ffee0000000702",
          "clinicId": "66d0a1f0c0ffee0000000102",
          "destinationWarehouseId": "66d0a1f0c0ffee0000000203",
          "version": 3
        }
      ],
      "hasMore": true,
      "nextCursor": "MjAyNi0wOS0xMlQxNDowNTowMC4wMDBafDY2ZDBhMWYwYzBmZmVlMDAwMDAwMDQ5OQ"
    }
    ```

    Los pedidos vienen del más antiguo al más reciente. El cursor es opaco: no lo decodifiques ni lo construyas. Consulta [Paginación](/docs/es/api-reference/pagination) para ver todas las reglas.
  </Step>
</Steps>

## Maneja errores y límites

Dos hábitos hacen que una integración sea robusta desde el primer día:

1. **Decide según `code`, no según el texto.** Todo error es un documento de problema con un `code` estable y un `requestId`. Registra ambos. Consulta [Errores](/docs/es/api-reference/errors).
2. **Respeta el límite de uso.** Cada clave tiene un presupuesto de solicitudes por minuto. Ante un `429`, espera los segundos que indica `Retry-After` y vuelve a intentarlo. Consulta [Límites de uso](/docs/es/api-reference/rate-limits).

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Scopes" icon="shield-halved" href="/docs/es/api-reference/scopes">
    Recetas de mínimo privilegio para integraciones comunes.
  </Card>

  <Card title="Exportaciones del resumen gerencial" icon="file-csv" href="/docs/es/api-reference/exports">
    Pide un CSV, consulta su estado y verifica su checksum.
  </Card>

  <Card title="Webhooks" icon="bell" href="/docs/es/api-reference/webhooks">
    Recetas de consulta periódica para pedidos, entregas y existencias.
  </Card>

  <Card title="Servidor MCP" icon="plug" href="/docs/es/mcp/introduction">
    MCP usa un flujo separado de ingreso y autorización con OAuth.
  </Card>
</CardGroup>


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