---
title: API
description: API Documentation for (Native) Application Developers
category: dev
---
This document describes the API provided by all Let's Connect!/eduVPN services.
The API can be used by applications integrating with the VPN software, making
it easier for users to start using the VPN.
# Instance Discovery
This document assumes you already have a FQDN to connect to, e.g. specified by
the user, but in order to allow applications to create a list of VPN services
available to the user to connect to, we also documented
[Instance Discovery](INSTANCE_DISCOVERY.md).
# Standards
OAuth 2.0 is used to provide the API. The following documents are relevant for
implementations and should be followed except when explicitly stated
differently:
* [The OAuth 2.0 Authorization Framework](https://tools.ietf.org/html/rfc6749);
* [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://tools.ietf.org/html/rfc6750);
* [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/rfc8252);
* [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636)
Implementing OAuth 2.0 correctly in native apps is not easy. There are a number
of sample libraries available for various platforms that can be used as a
basis:
* [Android](https://github.com/openid/AppAuth-Android)
* [iOS](https://github.com/openid/AppAuth-iOS)
* [Windows](https://github.com/googlesamples/oauth-apps-for-windows)
With this library it is very important that you handle all standard OAuth
"error" conditions regarding expired, invalid or revoked tokens.
# Definitions
A VPN service running at a particular domain is called an _instance_, e.g.
`demo.eduvpn.nl`. An instance can have multiple _profiles_, e.g.
`employees` and `administrators`.
# API Discovery
The OAuth and API endpoints can be discovered by requesting a JSON document
(`info.json`) from the instance, based on the `base_uri`, e.g.
`demo.eduvpn.nl`. As an example, here is the content of
`https://demo.eduvpn.nl/info.json`:
{
"api": {
"http://eduvpn.org/api#2": {
"api_base_uri": "https://demo.eduvpn.nl/portal/api.php",
"authorization_endpoint": "https://demo.eduvpn.nl/portal/_oauth/authorize",
"token_endpoint": "https://demo.eduvpn.nl/portal/oauth.php/token"
}
}
}
When fetching the `info.json` file, _redirects_, e.g. `301`, `302`, `303`,
MUST be followed.
# Authorization Endpoint
The `authorization_endpoint` is used to obtain an authorization code. The
following query parameters MUST be specified on the authorization request:
* `client_id`: the ID that was registered, see below;
* `redirect_uri`; the URL that was registered, see below;
* `response_type`: always `code`;
* `scope`: this is always `config`;
* `state`: a secure random string suitable for cryptography purposes, to avoid
CSRF;
* `code_challenge_method`: always `S256`;
* `code_challenge`: the code challenge (see RFC 7636).
The authorization request is then opened using the platform's default browser.
Eventually the `redirect_uri` is called where the initiating application can
extract the authorization code.
All error conditions MUST be handled according to the OAuth specification(s).
# Token Endpoint
The `token_endpoint` is used to exchange the authorization code for an access
and refresh token. It is also used to retrieve new access tokens when the
current access token expires.
The application MUST reauthorize, i.e. throw away all tokens and send a new
authorization request, when:
1. The access token did not expire yet, but was rejected by the API endpoint;
2. The access token expired, but obtaining a new one using the refresh token
failed.
All error conditions MUST be handled according to the OAuth specification(s).
# Using the API
The API is pragmatic "REST", keeping things as simple as possible without
obsessing about the proper HTTP verbs. There are no `PUT` and `DELETE`
requests. Only `GET`, to retrieve information without affecting the state of
the service, and `POST` to modify the server state.
The requests always return `application/json`. The `POST` requests MUST be sent
encoded as `application/x-www-form-urlencoded`.
The API can be used with the access tokens obtained using the OAuth flow as
documented above. The following API calls are available:
- Get a list of profiles available for the user (`/profile_list`);
- Obtain a new X.509 client certificate and private key (`/create_keypair`);
- Obtain OpenVPN profile configuration (`/profile_config`);
- Verify whether an existing X.509 client certificate can be used to connect
to the VPN (`/check_certificate`).
- Check if there are any messages available to show to the user
(`/system_messages`)
All error conditions MUST be handled according to the OAuth specification(s).
## API Calls
### Multi Language Support
For the calls listed below, applications MUST check if the mentioned value is
a string, or an object. In case of an object, the language best matching
the application language SHOULD be chosen. If that language is not available,
the application SHOULD fallback to `en` or `en-US`. If neither of those is
available, it is up to the application to pick one it deems best.
- `/profile_list`, the `display_name` field;
- `/system_messages`, the `message` field.
An example:
"display_name": {
"nl": "Internettoegang",
"en-US": "Internet Access"
}
### Date / Time Formats
Any occurrence of data and/or time has the
[ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Combined_date_and_time_representations)
format. It is used by the following API calls:
- `/system_messages`
### Profile List
This call will show the available VPN profiles for this instance. This will
allow the application to show the user which profiles are available.
$ curl -H "Authorization: Bearer abcdefgh" \
https://demo.eduvpn.nl/portal/api.php/profile_list
The response looks like this:
{
"profile_list": {
"data": [
{
"display_name": "Internet Access",
"profile_id": "internet",
"default_gateway": true,
}
],
"ok": true
}
}
**NOTE**: `default_gateway` is available since vpn-user-portal >=
[2.3.0](https://github.com/eduvpn/vpn-user-portal/blob/v2/CHANGES.md) and
indicates whether the profile expects all client traffic to go over the VPN.
### Create a Key Pair
**NOTE**: an obtained key pair is valid for ALL _profiles_ of a particular
_instance_, so if an instance has multiple profiles, only one key pair is
needed.
**NOTE**: on old(er) servers the `display_name` POST parameter is required, on
up-to-date servers the parameter is ignored.
$ curl -H "Authorization: Bearer abcdefgh" \
-d 'display_name=OAuth' https://demo.eduvpn.nl/portal/api.php/create_keypair
The call will create a certificate and private key and return them:
{
"create_keypair": {
"data": {
"certificate": "-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----",
"private_key": "-----BEGIN PRIVATE KEY----- ... -----END PRIVATE KEY-----"
},
"ok": true
}
}
The certificate and the private key SHOULD be stored in the platform's
"key store" in such a way that the user can **NOT** export the private key.
In traditional OpenVPN client configuration files, the certificate would be
placed in the `...` inline section, and the private key in the
`...` section.
### Profile Config
Only get the profile configuration without certificate and private key.
$ curl -H "Authorization: Bearer abcdefgh" \
"https://demo.eduvpn.nl/portal/api.php/profile_config?profile_id=internet"
The response will be an OpenVPN configuration file without the `` and
`` fields.
Starting from vpn-user-portal >= 2.1.1, an optional parameter `remote_strategy`
can be specified that takes an integer. It determines which `remote` lines are
returned as part of the generated configuration file. Value `0` takes the first
UDP/TCP ports of the "normal" and "special" sets. Value `1` takes random ports
from both the "normal" and "special" sets. Value `2` returns them _all_. The
default is `1`. Only change this if you know what you are doing! More on
`remote` lines in the VPN configuration can be read [here](SCALING.md#client).
### Check Certificate
A call is available to check whether an already obtained certificate will be
accepted by the VPN server. There are a number of reasons why this may not be
the case:
- The certificate does not exist (anymore) (`certificate_missing`);
- The certificate is not yet valid (`certificate_not_yet_valid`) or not
valid anymore (`certificate_expired`).
The client MAY implement this call, but MAY also opt to attempt to connect and
handle a connection rejection by attempting to obtain a new X.509 certificate /
key using the `/create_keypair` call and retry the connection.
API call:
$ curl -H "Authorization: Bearer abcdefgh" \
"https://demo.eduvpn.nl/portal/api.php/check_certificate?common_name=fd2c32de88c87d38df8547c54ac6c30e"
The `common_name` is the value of the X.509 certificate's common name (CN)
already in possession of the client.
The response looks like this:
{
"check_certificate": {
"data": {
"is_valid": true
},
"ok": true
}
}
Here, `is_valid` can also be `false` if the certificate won't be accepted by
the server. There MAY be a `reason` field that indicates the reason for the
certificate to not be valid. The `reason` field is only there when `is_valid`
is `false`:
{
"check_certificate": {
"data": {
"is_valid": false,
"reason": "certificate_missing"
},
"ok": true
}
}
### System Messages
$ curl -H "Authorization: Bearer abcdefgh" \
https://demo.eduvpn.nl/portal/api.php/system_messages
The application is able to access the `system_messages` endpoint to see if
there are any notifications available.
All messages have the type `notification`. All messages have a `date_time`
field containing the date the message was created.
An example response:
{
"system_messages": {
"data": [
{
"date_time": "2018-12-10T14:10:30Z",
"message": "This is the MOTD!",
"type": "notification"
}
],
"ok": true
}
}
## OAuth Client Registration
A list of OAuth client registrations that are available for all installations
can be found [here](https://github.com/eduvpn/vpn-user-portal/blob/v2/src/OAuthClientInfo.php).
Administrators MAY define additional OAuth clients in the
`/etc/vpn-user-portal/config.php` configuration file.