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

# Authorization

> How to obtain a Personal Access Token and authenticate your requests.

ClosedVPN authenticates API requests with a **Personal Access Token (PAT)** sent in
the `Authorization` header. This page covers how to get your first token, how to use
it, and how to manage its lifecycle.

## Getting your first token

Creating a token requires an authenticated session, so the very first token comes
from signing in. There are two routes.

<Tabs>
  <Tab title="From the web application">
    1. Sign in at [closedvpn.io](https://closedvpn.io) using the magic link sent to
       your email address.
    2. Open the **Profile** page.
    3. Choose **Create token**, give it a name, and optionally set an expiry.
    4. Copy the token immediately — it is shown only once.
  </Tab>

  <Tab title="Over the API">
    The magic link flow is the only way to authenticate without an existing token.

    **1. Request a link.** This endpoint needs no credential. If the address has no
    account, one is created.

    ```bash theme={null}
    curl -X POST https://closedvpn.io/auth/send-magic-link \
      -H "Content-Type: application/json" \
      -d '{"email": "john@example.com"}'
    ```

    **2. Follow the link.** The email contains a URL carrying a `token` query
    parameter, valid for 24 hours and usable once. Visiting it exchanges the token
    for a session cookie.

    ```bash theme={null}
    curl -c cookies.txt \
      "https://closedvpn.io/auth/verify-magic-link?token=THE_TOKEN_FROM_THE_EMAIL"
    ```

    **3. Create a PAT** using that session.

    ```bash theme={null}
    curl -b cookies.txt -X POST https://closedvpn.io/auth/generate-pat \
      -H "Content-Type: application/json" \
      -d '{"tokenName": "CI Pipeline", "expiryDays": 90}'
    ```

    ```json theme={null}
    {
      "message": "PAT generated",
      "pat": "a3f5b8c1d2e4f607",
      "note": "Save this PAT securely; it won't be shown again!"
    }
    ```
  </Tab>
</Tabs>

<Warning>
  The value in the `pat` field is the only time the plaintext token is available. It
  is stored as a bcrypt hash, so it cannot be recovered. If it is lost, revoke it and
  create a new one.
</Warning>

## Using your token

Send the token as a Bearer credential on every request.

```bash theme={null}
curl https://closedvpn.io/auth/get-orgs \
  -H "Authorization: Bearer YOUR_PAT_HERE"
```

The first time a magic link is verified, a default organization named **My Org** is
created with you as its owner, and the first available VPN is assigned to it. So a
new account can call organization endpoints immediately.

## Organization context

Endpoints that act on a single organization either take an explicit `org_id`, or
fall back to your **selected organization**.

```bash theme={null}
curl -X POST https://closedvpn.io/auth/select-org \
  -H "Authorization: Bearer YOUR_PAT_HERE" \
  -H "Content-Type: application/json" \
  -d '{"orgId": "60c72b2f5f1b2c001c8e4b1a"}'
```

`GET /auth/vpn-exit-nodes` requires a selection and returns `400` without one.
`GET /auth/threat-prevention-stats` accepts an optional `org_id` and falls back to
the selection.

## Roles

Each member of an organization is either an **owner** or a **member**. Owner-only
operations are: updating and deleting the organization, adding members, changing
roles, and removing members. Attempting one as a member returns `403`.

Two guards apply to owners: you cannot change your own role, and the last remaining
owner can neither leave nor delete their only organization.

## Managing tokens

<AccordionGroup>
  <Accordion title="List your tokens">
    [`GET /auth/get-pats`](/closedvpn/endpoint/personal-access-tokens/get-pats)
    returns your active tokens. Only the bcrypt hash of each is returned, never the
    plaintext. Tokens whose expiry has passed are deactivated as a side effect of
    this call and excluded from the result.
  </Accordion>

  <Accordion title="Revoke a token">
    [`POST /auth/delete-pat`](/closedvpn/endpoint/personal-access-tokens/delete-pat)
    deactivates a token. Pass the **hash** returned by `GET /auth/get-pats` in the
    `token` field, not the plaintext value.
  </Accordion>

  <Accordion title="Expiry">
    Pass `expiryDays` when creating a token to set a lifetime. Omit it for a token
    that never expires. An expired token returns `403`.
  </Accordion>
</AccordionGroup>

<Note>
  `POST /auth/logout` clears the browser session cookie only. It does not revoke
  Personal Access Tokens and has no effect on API clients. Use `/auth/delete-pat`
  to revoke a token.
</Note>

## Authentication errors

| Status | Meaning                                                        |
| :----- | :------------------------------------------------------------- |
| `401`  | No credential was supplied.                                    |
| `403`  | The token is invalid, expired, or has been deactivated.        |
| `403`  | The token is valid, but you lack permission for this resource. |

<Tip>
  A `403` where you expect success usually means the token is fine but your role is
  insufficient, or you are not an active member of the target organization.
</Tip>
