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

# Authentication

> API keys, what they can reach, and how to keep them safe.

Vizito accepts three credentials on the same API, side by side. Which one you use is entirely your choice.

| Method                      | Header                                 | Secret lives          |
| --------------------------- | -------------------------------------- | --------------------- |
| **API key** *(recommended)* | `Authorization: Bearer vzk_...`        | With Vizito, hashed   |
| **Microsoft Entra ID**      | `Authorization: Bearer <access token>` | In your own directory |
| Username and password       | `Authorization: Basic ...`             | With Vizito           |

<Note>
  Basic authentication with a Backoffice login still works and existing integrations keep running. It is no longer the recommended way: it hands out a person's password, it cannot be rotated without locking that person out, and it dies with their account. Use an API key instead.
</Note>

## Creating an API key

<Steps>
  <Step title="Open the Integrations page as a global admin">
    [backoffice.vizito.be → Integrations](https://backoffice.vizito.be/#/app/integrations). Only global admins can manage keys — a key can span several locations and outlives whoever created it, so issuing one is an account-wide decision.
  </Step>

  <Step title="Fill in the form">
    * **Name** — say which integration it is for. It is what you will read on the "last used" list a year from now.
    * **Locations** — tick every location this integration needs. It gets nothing outside them.
    * **Expiry** — never, 30 days, 90 days or a year.
    * **IP addresses** *(optional)* — single addresses or CIDR ranges the key may be used from.
  </Step>

  <Step title="Copy the key">
    It is shown once. Vizito stores only a SHA-256 hash and cannot show it again — lose it and you create a new one.
  </Step>
</Steps>

<Card title="Manage your API keys" icon="arrow-up-right-from-square" href="https://backoffice.vizito.be/#/app/integrations">
  Create, revoke and delete keys on the Backoffice Integrations page.
</Card>

## Using the key

A key looks like `vzk_<key id>_<secret>`. Send it as a bearer token:

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.vizito.eu/api/companiesList \
    -H "Authorization: Bearer vzk_1a2b3c4d5e6f7890_..."
  ```

  ```javascript Node.js theme={null}
  const response = await fetch('https://api.vizito.eu/api/companiesList', {
    headers: { Authorization: `Bearer ${process.env.VIZITO_API_KEY}` }
  });
  const locations = await response.json();
  ```

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

  response = requests.get(
      "https://api.vizito.eu/api/companiesList",
      headers={"Authorization": f"Bearer {os.environ['VIZITO_API_KEY']}"},
  )
  locations = response.json()
  ```

  ```php PHP theme={null}
  $ch = curl_init('https://api.vizito.eu/api/companiesList');
  curl_setopt($ch, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . getenv('VIZITO_API_KEY')]);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  $locations = json_decode(curl_exec($ch), true);
  ```
</CodeGroup>

If a bearer token is awkward in your tooling, the same value is accepted in an `X-API-Key` header.

## What a key may do

A key is **its own principal**, not a stand-in for the admin who created it. On the locations it was granted it has full administrator access; outside them it has none at all. It keeps working when that admin is demoted, moved or leaves — an integration should not break because somebody changed jobs.

There are three things a key deliberately cannot do, so that a leaked key cannot escalate into an account:

<Columns cols={3}>
  <Card title="Create locations" icon="ban" horizontal>
    It works inside the locations it was given; it does not hand out new ones.
  </Card>

  <Card title="Manage users" icon="ban" horizontal>
    No creating Backoffice logins, changing roles or resetting passwords.
  </Card>

  <Card title="Manage API keys" icon="ban" horizontal>
    No minting replacements, so revoking one actually means something.
  </Card>
</Columns>

Those endpoints answer `403` for a key, whatever it covers. Do them in the Backoffice.

## Every restriction is checked per request

Revoking a key, letting it expire, narrowing its locations or changing its IP list takes effect on the very next call. There is no cache to wait out.

<Warning>
  **Every refusal answers the same way**: `403` with `{"error": "Invalid API key"}` — whether the key is unknown, revoked, expired, used from the wrong IP address or aimed at a location it does not cover. That is deliberate: a more specific message would tell whoever holds a stolen key exactly what to try next. The real reason is in the Vizito server log; if you are stuck, [support](mailto:support@vizito.eu) can read it back to you.
</Warning>

When debugging a `403`, work down this list:

1. Is the header exactly `Authorization: Bearer <key>`, one space, nothing truncated?
2. Is the key still active and not past its expiry date? Both are visible in the Backoffice.
3. Is the calling server's IP address in the key's whitelist — including the new one you deployed to last week?
4. Does the key cover the location you are addressing? Check against `GET /companiesList`.

Thirty refused attempts from one IP address in fifteen minutes gets you `429` for the rest of the window. Successful requests never count towards it.

## Microsoft Entra ID

If you would rather not store a Vizito secret at all, register your application in your own Entra ID directory and let Vizito trust tokens it issues.

<Steps>
  <Step title="Register the application in Azure">
    Add a client secret or a certificate, and note the **Directory (tenant) ID** and the **Application (client) ID**.
  </Step>

  <Step title="Register it in Vizito">
    On the Integrations page, create a credential with authentication type **Microsoft Entra ID** and enter both ids. Pick its locations exactly as you would for an API key.
  </Step>

  <Step title="Get a token">
    ```http theme={null}
    POST https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token
    Content-Type: application/x-www-form-urlencoded

    grant_type=client_credentials
    &client_id=<client id>
    &client_secret=<client secret>
    &scope=<client id>/.default
    ```
  </Step>

  <Step title="Call the API with it">
    `Authorization: Bearer <access token>`. Tokens are valid for about an hour; ask for a fresh one when it expires.
  </Step>
</Steps>

Vizito verifies the signature against Microsoft's published keys, that the token comes from the registered tenant, that its audience is the registered application, and that it was issued through the client credentials grant rather than delegated from a user. From there on it behaves exactly like an API key: same locations, same permissions, same restrictions.

## Keeping keys healthy

<AccordionGroup>
  <Accordion title="One key per integration" icon="key">
    Sharing a key across two systems means revoking it breaks both, and "last used" tells you nothing. Keys are free — make one each.
  </Accordion>

  <Accordion title="Give it only the locations it needs" icon="building">
    A key for a badge printer at one site has no business reading the visitor log of another.
  </Accordion>

  <Accordion title="Restrict by IP where you can" icon="network-wired">
    A server integration calls from a known address. Whitelisting it makes a leaked key useless anywhere else — just remember to update the list when you move servers.
  </Accordion>

  <Accordion title="Check 'last used' before revoking" icon="clock-rotate-left">
    The Integrations page shows when each key was last seen. It is the quickest way to tell a live integration from a forgotten experiment.
  </Accordion>
</AccordionGroup>


## Related topics

- [Introduction](/api-reference/introduction.md)
- [Errors](/api-reference/errors.md)
- [Fetching a location](/api-reference/locations/fetching-a-location.md)
- [Quickstart](/documentation/quickstart.md)
