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

# API quickstart

> Make your first calls to the muveya API with cURL, JavaScript or Python: identify your key, list locations and catalog items, read an order and walk through pages.

This page takes you from a new API key to a paginated list of orders. Every example runs on your server, never in a browser.

## Before you start

* An API key with the scopes `clinics:read`, `catalog:read` and `orders:read`. See [Authentication](/docs/en/api-reference/authentication) for how to get one.
* One of: `curl`, Node.js 18 or later (it includes `fetch`), or Python 3 with the `requests` package (`pip install requests`).
* The JavaScript examples use top-level `await`: save them in a file ending in `.mjs`.

## Steps

<Steps>
  <Step title="Put the key in an environment variable">
    Never write the key in your code. Export it in the shell (or load it from your secret manager):

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

    All the examples below read `MUVEYA_API_KEY` and call `https://api.muveya.com`.
  </Step>

  <Step title="Identify the key">
    `GET /v1/me` needs no scope. It tells you which dental clinic the key reads and which scopes it holds.

    <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 Response theme={null}
    {
      "workspaceId": "66d0a1f0c0ffee0000000001",
      "environment": "test",
      "scopes": ["clinics:read", "catalog:read", "orders:read"]
    }
    ```

    A `401` with `api_keys.invalid` means the header or the key is wrong. See [Authentication failures](/docs/en/api-reference/authentication#authentication-failures).
  </Step>

  <Step title="List your locations">
    `GET /v1/clinics` (scope `clinics:read`) returns every location of the dental clinic in one page. `GET /v1/warehouses` works the same way for warehouses.

    <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 Response theme={null}
    {
      "data": [
        { "clinicId": "66d0a1f0c0ffee0000000101", "name": "Clínica Norte", "status": "active" },
        { "clinicId": "66d0a1f0c0ffee0000000102", "name": "Clínica Sur", "status": "active" }
      ],
      "hasMore": false
    }
    ```

    Keep a map of `clinicId` to `name`: orders and analytics refer to locations by id.
  </Step>

  <Step title="List catalog items">
    `GET /v1/catalog/items` (scope `catalog:read`) returns every catalog item, whatever its `status`, in one page.

    <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 Response 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
    }
    ```

    This key has no `catalog.cost:read`, so the cost fields are absent. With that scope, each item also carries `costStatus` and, when a cost is recorded, `cost`, `currency` and `costMeasurement`. Check `costStatus` before using an amount: see [Catalog costs](/docs/en/catalog/costs).
  </Step>

  <Step title="Read an order">
    Take an `orderId` from `GET /v1/orders` and read it with `GET /v1/orders/{orderId}` (scope `orders:read`). The detail includes the lines and the approval plan.

    <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 Response 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
    }
    ```

    The order `value` and the `patientRef` of a clinical order are never returned on `/v1`. An order of another dental clinic, or an id that does not exist, answers `404` with the code `orders.not_found`.
  </Step>

  <Step title="Walk through pages">
    `GET /v1/orders` is paginated. Ask for up to 200 orders per page, and pass `nextCursor` back as `cursor` while `hasMore` is `true`. Send the same filters with every page.

    <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 First page 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"
    }
    ```

    Orders come oldest first. The cursor is opaque: do not decode or build it. See [Pagination](/docs/en/api-reference/pagination) for every rule.
  </Step>
</Steps>

## Handle errors and limits

Two habits make an integration robust from day one:

1. **Branch on `code`, not on text.** Every error is a problem document with a stable `code` and a `requestId`. Log both. See [Errors](/docs/en/api-reference/errors).
2. **Respect the rate limit.** Each key has a budget of requests per minute. On a `429`, wait the number of seconds in `Retry-After` and try again. See [Rate limits](/docs/en/api-reference/rate-limits).

## Next steps

<CardGroup cols={2}>
  <Card title="Choose scopes" icon="shield-halved" href="/docs/en/api-reference/scopes">
    Least-privilege recipes for common integrations.
  </Card>

  <Card title="Export the briefing" icon="file-csv" href="/docs/en/api-reference/exports">
    Request a CSV, poll it and verify its checksum.
  </Card>

  <Card title="Stay in sync without webhooks" icon="bell" href="/docs/en/api-reference/webhooks">
    Polling recipes for orders, deliveries and stock.
  </Card>

  <Card title="Connect an AI assistant" icon="plug" href="/docs/en/mcp/introduction">
    MCP uses a separate OAuth sign-in and authorization flow.
  </Card>
</CardGroup>


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