# Getting Started with the API

## Base URL

All API requests are made to the following base URL:

```http
https://api.kpnthings.com
```

The API currently has a single version. All changes are backward-compatible.

Requests use standard HTTP methods (`GET`, `POST`, `PUT`, `DELETE`), and both requests and responses are in JSON format.

## Authentication

Access to the KPN Things API is secured through **GRIP**, KPN’s identity and access management platform. API keys and tokens are managed in GRIP, independently of individual user accounts.

Every request to the KPN Things API must be authenticated with a **bearer token** (an access token) obtained from GRIP using an API key.

### API Keys in GRIP

* An **API Key** is a standalone entity within a GRIP tenant (your customer environment).
* API Keys are **not user-bound**; they are service credentials that can be used by applications or systems.
* Best practice: create separate API keys for each system or integration that needs access.

### Access Tokens

To call the KPN Things API, you must exchange your API Key for a short-lived **access token**:

* Tokens are valid for **1 hour**.
* Include the token in every API request via the `Authorization` header:

```http
Authorization: Bearer <accessToken>
```

* There are no refresh tokens. When a token expires, simply request a new one from GRIP.

More background on the authentication model used is available in the [GRIP Knowledge Base](https://grip.kpn.com/en/documentation/technicalIntegration) under Machine Authentication:

* [Machine Authentication Introduction](https://grip.kpn.com/en/documentation/article/machineauthenticationintroduction#heading-practical-implications)
* [Machine Authentication OAuth](https://grip.kpn.com/en/documentation/article/machineauthenticationoauth#heading-the-client)

### **Required Values**

When requesting an access token from GRIP, you will need the following:

<table><thead><tr><th width="186">Value</th><th>Description</th></tr></thead><tbody><tr><td>GRIP Tenant ID</td><td>The unique identifier of your GRIP environment.</td></tr><tr><td>Application ID</td><td><p>The ID of the service you want to access. </p><p>For KPN Things, always use:<br><code>4dc82561-f65f-523g-dek9-6c79ec314f02</code></p></td></tr><tr><td>Client ID</td><td>The ID of your API Key.</td></tr><tr><td>Client Secret</td><td>The secret associated with your API Key.</td></tr></tbody></table>

### Step 1: Find your GRIP Tenant ID <a href="#api-access-to-customer-environments-as-a-reseller" id="api-access-to-customer-environments-as-a-reseller"></a>

1. Log in to [MijnKPN Zakelijk](https://mijnzakelijk.kpn.com/).
2. Hover over your name in the top right corner.
3. In the overflow menu your **GRIP Tenant ID** is displayed right above your name.

### Step 2: Create an API Key in GRIP

To create an API key, you need administrator access to the GRIP portal.

* If you have a KPN Developer account, you are automatically an administrator.
* If you have a MijnKPN Zakelijk account, you may need to request access from your local administrator.

#### 2.1 Open API Key Management

* Log in to the **GRIP Portal**.
* Open the **Admin panel** from the role dropdown.
* In the left-hand menu, go to **Identity → API Keys**.
* Click **Add** to create a new API Key.

<figure><img src="/files/S76LEVVZb8ZYIlCg911E" alt=""><figcaption></figcaption></figure>

#### 2.2. Create new API Key

* Enter a **Display name** and **Description** for your API Key.
* Click **Add** to create it.

<figure><img src="/files/jZFaDVvyMlZOpkJQU8O3" alt=""><figcaption></figcaption></figure>

#### 2.3. Grant access to KPN Things

* On the API Key details page, open the **Services and Roles** tab.
* Click **Add a service**.

<figure><img src="/files/9fbkxNwPJXDM23tPv7YZ" alt=""><figcaption></figcaption></figure>

* Select **KPN Things Portal** from the list and click **Add**.

<figure><img src="/files/W1q3qbkzXCxvOfDW5qbb" alt=""><figcaption></figcaption></figure>

* Assign the desired **role** and **access level** to the API Key.

<div align="left"><figure><img src="/files/jiV97mvGeUsVzO9Ug4w8" alt="" width="401"><figcaption></figcaption></figure></div>

* Click **Save** to apply.

<figure><img src="/files/LslTJj1Vmb5hD9Lkqgnc" alt=""><figcaption></figcaption></figure>

#### 2.4 Retrieve the Credentials

* Go back to the **General Information** tab of your new API Key.
* Copy the **ID** (Client ID) and **Secret** (Client Secret).
* Store them securely — you will use them to request access tokens.

<figure><img src="/files/wLkCobXB9pbhSS5X393a" alt=""><figcaption></figcaption></figure>

### **Step 3: Request an Access Token**

With your Tenant ID, Application ID, Client ID, and Client Secret, you can now request an access token from GRIP. Refer to the [GRIP API Documentation](https://developer.kpn.com/documentation/kpn-grip-api-documentation) for details, including OpenAPI (Swagger) specifications.

**Example Request:**

```http
POST https://auth.grip-on-it.com/api/v3/idp/oidc/token
Content-Type: application/json

{
  "grant_type": "client_credentials",
  "audience": "4dc82561-f65f-523g-dek9-6c79ec314f02",
  "client_id": "<your-api-key-id>",
  "client_secret": "<your-api-key-secret>"
}
```

**Example Response:**

```json
{
  "access_token": "<your-access-token>",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

Use this token in the `Authorization` header for all subsequent API requests.

| **Error**                               | **Solution**                                                                          |
| --------------------------------------- | ------------------------------------------------------------------------------------- |
| Feature user does not exist anymore.    | <p>Check your API Key ID or Secret, <br>they may be incorrect.</p>                    |
| Feature service does not exist anymore. | <p>Add the KPN Things Portal service <br>to your API Key through the GRIP Portal.</p> |

## API access to customer environments as a reseller <a href="#api-access-to-customer-environments-as-a-reseller" id="api-access-to-customer-environments-as-a-reseller"></a>

As a reseller of KPN Things, you can use the KPN Things APIs to manage your customers’ environments on their behalf. To do this, you must specify the customer context for each API call by including the following header:

```http
KPN-Things-Security-Context: client:uuid:{{customerID}
```

Here, `{{customerID}}` is the UUID that identifies the customer whose environment you want to manage.

When this header is provided, KPN Things validates its syntax and confirms that your account is authorized as a reseller for the specified customer.

If either validation fails, the API request will be rejected. Please note that only one `KPN-Things-Security-Context` header can be included per request; multiple values are not supported.

{% hint style="warning" %}
💎 **Add-on feature**\
To get started as a reseller and set up environments within KPN Things for your own customers, you will need the [Customer Management](https://docs.kpnthings.com/kpn-things/getting-started/tutorials/accounts-and-projects/customer-management) add-on feature.&#x20;
{% endhint %}

## Pagination

The API uses **cursor-based pagination**. This approach ensures consistent performance when navigating large datasets.

When requesting a list of resources, the response may contain `next` and `prev` links. These links contain an opaque cursor that identifies the next or previous page.

```json
{
  "items": [ ... ],
  "next": "https://api.kpnthings.com/devices?cursor=abc123",
  "prev": "https://api.kpnthings.com/devices?cursor=xyz789"
}
```

* Use the `next` link to load the following page.
* Use the `prev` link to load the previous page.
* The API does **not** provide the total number of results or pages.

You can control the maximum number of results per page using the `limit` query parameter:

```http
GET /downlinks?limit=50
```

## Filtering

You can filter results by adding query parameters. The most common filters are:

| Parameter        | Description                               |
| ---------------- | ----------------------------------------- |
| `createdBefore`  | Items created before this timestamp       |
| `createdSince`   | Items created at or after this timestamp  |
| `modifiedBefore` | Items modified before this timestamp      |
| `modifiedSince`  | Items modified at or after this timestamp |
| `q`              | Free-text search across common fields     |

**Example:**

```http
GET /downlinks?modifiedSince=2025-09-01T00:00:00Z
```

## Sorting

Use the `sort` parameter to order results. Multiple attributes can be combined in a comma-separated list. Prefix an attribute with `-` to sort in descending order.

**Examples:**

* `sort=createdAt,id` → sort by `createdAt` ascending, then `id` ascending
* `sort=-createdAt,id` → sort by `createdAt` descending, then `id` ascending

**Example Request:**

```http
GET /downlinks?modifiedSince=2025-09-01T00:00:00Z&limit=100&sort=-modifiedAt
```

## **Error Handling**

The KPN Things API uses the [Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc7807) format (`application/problem+json`) to return error information. This provides a consistent structure for all error responses.

### **Response Format**

An error response includes standard fields defined by RFC 7807, as well as optional details when available.

**Example Error Response:**

```json
{
  "title": "Invalid request",
  "status": 400,
  "detail": "The field 'limit' must be greater than zero.",
  "instance": "/downlinks?limit=-1"
}
```

### **Standard Fields**

<table><thead><tr><th width="348">Field</th><th>Description</th></tr></thead><tbody><tr><td><code>title</code></td><td>A short, human-readable summary of the problem type.</td></tr><tr><td><code>status</code></td><td>The HTTP status code of the error response.</td></tr><tr><td><code>detail</code></td><td>A human-readable explanation specific to this occurrence of the error.</td></tr><tr><td><code>instance</code></td><td>The request URI where the error occurred.</td></tr></tbody></table>

### **Common Error Codes**

* **400 Bad Request** — The request is invalid (e.g., malformed parameters, invalid values).
* **401 Unauthorized** — The request is missing a valid access token.
* **403 Forbidden** — The token is valid, but you do not have permission to perform this action.
* **404 Not Found** — The requested resource does not exist.
* **409 Conflict** — A conflicting operation is already in progress, or resource constraints prevent the request.
* **429 Too Many Requests** — You have hit a rate limit.
* **500 Internal Server Error** — An unexpected error occurred.

### **Handling Errors in Clients**

When consuming the API, clients should:

1. Check the **HTTP status code** to determine the general error category.
2. Inspect the `title` field for programmatic handling of known error types.
3. Display or log the `detail` field to help users or developers troubleshoot.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.kpnthings.com/kpn-things/general-functions/apis/getting-started-with-the-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
