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

# Creating a VPN Certificate

> Issue and download an OpenVPN client profile for a member.

Each member of an organization gets one OpenVPN client profile. A single endpoint
both issues and returns it:

```
GET /auth/download-certificate?org_id=<ORG_ID>
```

The first call generates the certificate on the VPN host and consumes one licence.
Every later call returns the same certificate and consumes nothing, so the endpoint
is safe to call repeatedly.

## Prerequisites

Before a certificate can be issued:

* You are an **active member** of the organization.
* A VPN configuration is **assigned** to that organization.
* That VPN's `commands` map defines a **`create_cert`** entry.
* The organization has at least one **free licence** (`used_licenses` \< `total_licenses`).

## Step 1 — Find your organization

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

Take the `_id` of the organization you want. The response also carries
`used_licenses` and `total_licenses`, so you can confirm capacity before issuing,
and `vpn_name` tells you which VPN is assigned.

```json theme={null}
{
  "orgs": [
    {
      "_id": "60c72b2f5f1b2c001c8e4b1a",
      "name": "My Org",
      "vpn_id": "60c72b2f5f1b2c001c8e4b2a",
      "vpn_name": "Frankfurt Node",
      "used_licenses": 3,
      "total_licenses": 10
    }
  ]
}
```

## Step 2 — Download the profile

```bash theme={null}
curl -OJ "https://closedvpn.io/auth/download-certificate?org_id=60c72b2f5f1b2c001c8e4b1a" \
  -H "Authorization: Bearer YOUR_PAT_HERE"
```

The response is served as `application/x-openvpn-profile` with a
`Content-Disposition` header naming the file, for example
`ClosedVPN-My Org.ovpn`. The `-OJ` flags above tell curl to honour that name.

## The response format

<Warning>
  The body is **not** a bare `.ovpn` file. The first line is a `# closedvpn-json`
  comment carrying the organization's exit nodes, followed by a blank line and then
  the OpenVPN profile.
</Warning>

```
# closedvpn-json {"vpnExitNodes":[{"id":"60c72b2f5f1b2c001c8e4b2a","name":"Frankfurt Node","location":"Frankfurt, Germany","coordinates":[50.1109,8.6821]}]}

client
dev tun
proto udp
remote vpn-fra.closedvpn.io 1194
...
```

OpenVPN ignores `#` comment lines, so the file works as-is with most clients. If
your tooling parses the profile strictly, strip the first two lines:

```bash theme={null}
tail -n +3 "ClosedVPN-My Org.ovpn" > profile.ovpn
```

Or read the exit-node metadata out of it:

```bash theme={null}
head -1 "ClosedVPN-My Org.ovpn" | sed 's/^# closedvpn-json //' | jq .
```

## Licences

A licence is consumed only when a **new** certificate is generated. `used_licenses`
increments at that point. Re-downloading an existing certificate does not consume
another.

Licences are never released. `used_licenses` only ever increases — removing a member
or having them leave does not free their licence — so an organization that cycles
through members will need `total_licenses` raised.

Once all licences are used, further members receive `403`. The message differs by
role: owners are told to acquire more licences, members are told to contact their
owner.

## Troubleshooting

<AccordionGroup>
  <Accordion title="400 — Organization ID is required">
    The `org_id` query parameter is missing.
  </Accordion>

  <Accordion title="400 — No VPN assigned to this organization">
    No VPN configuration is attached to the organization. Assign one, or create the
    organization with a `vpnId` — list the available VPNs with `GET /auth/vpns`.
  </Accordion>

  <Accordion title="400 — create_cert command not set in VPN config">
    The assigned VPN configuration has no `create_cert` entry in its `commands` map.
    An administrator must add it before certificates can be issued. Occurrences of
    `CLIENT_NAME` in the command are substituted at run time.
  </Accordion>

  <Accordion title="403 — Maximum number of certificates reached">
    All licences are in use. `used_licenses` is never decremented — removing a member
    or having them leave does not release their licence — so the only remedy is to
    increase `total_licenses`.
  </Accordion>

  <Accordion title="403 — No permission to access this organization">
    You are not an active member of the organization named by `org_id`.
  </Accordion>

  <Accordion title="404 — Organization not found">
    The `org_id` does not match any organization. Check the value against
    `GET /auth/get-orgs`. The same status is returned as `User not found` in the rare
    case where your own user record no longer exists.
  </Accordion>

  <Accordion title="404 — VPN config not found">
    The organization references a VPN configuration that no longer exists. An
    administrator must assign a valid VPN — list them with `GET /auth/vpns`.
  </Accordion>

  <Accordion title="500 — Failed to generate certificate">
    The platform reached the VPN host but the `create_cert` command failed. The
    `details` field carries the host's error output.
  </Accordion>

  <Accordion title="500 — No certificate received">
    The `create_cert` command ran without error but produced no output. The command
    itself is likely misconfigured on the VPN host.
  </Accordion>
</AccordionGroup>

## Checking the connection

Once connected, confirm the tunnel and read traffic counters with
[`GET /auth/vpn-user-stats`](/closedvpn/endpoint/statistics/vpn-user-stats).

```bash theme={null}
curl "https://closedvpn.io/auth/vpn-user-stats?org_id=60c72b2f5f1b2c001c8e4b1a" \
  -H "Authorization: Bearer YOUR_PAT_HERE"
```

<Note>
  When you are not connected, or the VPN host cannot be reached, the response is
  `{"connected": false}` with no other fields. Check `connected` before reading the
  rest.
</Note>
