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

# Leaf Link

> Configure API keys and register provider applications for Leaf Link, the embeddable widget that lets growers connect their agricultural data accounts.

Use the Leaf Link endpoints to create API keys for widget sessions and to register provider application credentials for embedded OAuth flows. This page is the reference for the backend setup that powers the Leaf Link UI components.

For conceptual background, see [Leaf Link](/components/leaf-link).

## Overview

Leaf Link widgets let your users connect their provider accounts directly from your application. To use them, you need:

1. **An API key** scoped to a Leaf user for widget authentication.
2. **Provider app registrations** so Leaf knows which provider credentials to use during the OAuth flow.

**Base URL:** `https://api.withleaf.io/services/usermanagement/api`

***

## API Keys

API keys authenticate Leaf Link widget sessions for a specific Leaf user.

### Endpoints

| Endpoint          | Method   | Path                   |
| ----------------- | -------- | ---------------------- |
| Get all API keys  | `GET`    | `/api-keys`            |
| Create an API key | `POST`   | `/api-keys`            |
| Revoke an API key | `DELETE` | `/api-keys/{apiKeyId}` |

***

### Get all API keys

`GET /api-keys`

Returns every API key associated with a Leaf user.

#### Parameters

| Parameter    | Type   | Required | Description                                  |
| ------------ | ------ | -------- | -------------------------------------------- |
| `leafUserId` | string | Yes      | The UUID of the Leaf user to query keys for. |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET \
    "https://api.withleaf.io/services/usermanagement/api/api-keys?leafUserId={leafUserId}" \
    -H "Authorization: Bearer {token}"
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/api-keys"
  headers = {"Authorization": f"Bearer {token}"}
  params = {"leafUserId": leaf_user_id}

  response = requests.get(url, headers=headers, params=params)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://api.withleaf.io/services/usermanagement/api/api-keys?leafUserId=${leafUserId}`,
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const keys = await response.json();
  ```
</CodeGroup>

#### Response

```json theme={null}
[
  {
    "key": "lk_abc123...",
    "expiresAt": "2025-10-01T00:00:00.000Z",
    "valid": true
  }
]
```

***

### Create an API key

`POST /api-keys`

Creates a new API key for widget authentication.

#### Request body

| Field         | Type    | Required | Description                                             |
| ------------- | ------- | -------- | ------------------------------------------------------- |
| `leafUserId`  | string  | Yes      | The UUID of the Leaf user.                              |
| `expiresIn`   | integer | No       | Lifetime in seconds. Minimum `900`. Defaults to 1 year. |
| `description` | string  | No       | A human-readable label for the key.                     |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST \
    "https://api.withleaf.io/services/usermanagement/api/api-keys" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -d '{
      "leafUserId": "{leafUserId}",
      "expiresIn": 86400,
      "description": "Production widget key"
    }'
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/api-keys"
  headers = {
      "Authorization": f"Bearer {token}",
      "Content-Type": "application/json",
  }
  body = {
      "leafUserId": leaf_user_id,
      "expiresIn": 86400,
      "description": "Production widget key",
  }

  response = requests.post(url, headers=headers, json=body)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.withleaf.io/services/usermanagement/api/api-keys",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        leafUserId,
        expiresIn: 86400,
        description: "Production widget key",
      }),
    }
  );
  const key = await response.json();
  ```
</CodeGroup>

#### Response

```json theme={null}
{
  "key": "lk_abc123...",
  "expiresAt": "2025-10-02T00:00:00.000Z",
  "valid": true
}
```

***

### Revoke an API key

`DELETE /api-keys/{apiKeyId}`

Permanently revokes an API key. This action cannot be undone.

#### Path parameters

| Parameter  | Type   | Required | Description                  |
| ---------- | ------ | -------- | ---------------------------- |
| `apiKeyId` | string | Yes      | The ID of the key to revoke. |

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE \
    "https://api.withleaf.io/services/usermanagement/api/api-keys/{apiKeyId}" \
    -H "Authorization: Bearer {token}"
  ```

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

  url = f"https://api.withleaf.io/services/usermanagement/api/api-keys/{api_key_id}"
  headers = {"Authorization": f"Bearer {token}"}

  response = requests.delete(url, headers=headers)
  ```

  ```javascript JavaScript theme={null}
  await fetch(
    `https://api.withleaf.io/services/usermanagement/api/api-keys/${apiKeyId}`,
    {
      method: "DELETE",
      headers: { Authorization: `Bearer ${token}` },
    }
  );
  ```
</CodeGroup>

***

## Provider App Information

Register your provider application credentials so Leaf Link widgets can initiate the OAuth flow on behalf of your users. All providers support the same CRUD operations, but the path shape differs depending on whether the provider uses a `clientEnvironment`.

### Endpoint pattern

Providers without `clientEnvironment` use this pattern:

| Endpoint           | Method   | Path                             |
| ------------------ | -------- | -------------------------------- |
| Get all apps       | `GET`    | `/app-keys/{Provider}`           |
| Get an app by name | `GET`    | `/app-keys/{Provider}/{appName}` |
| Create an app      | `POST`   | `/app-keys/{Provider}/{appName}` |
| Update an app      | `PUT`    | `/app-keys/{Provider}/{appName}` |
| Delete an app      | `DELETE` | `/app-keys/{Provider}/{appName}` |

<Note>
  For **CNHI**, **CNHI FieldOps**, and **John Deere**, the provider-specific endpoint pattern is:

  * `GET /app-keys/{Provider}`
  * `GET /app-keys/{Provider}/{appName}/{clientEnvironment}`
  * `POST /app-keys/{Provider}/{appName}/{clientEnvironment}`
  * `PUT /app-keys/{Provider}/{appName}/{clientEnvironment}`
  * `DELETE /app-keys/{Provider}/{appName}/{clientEnvironment}`

  The client environment is typically `STAGE` or `PRODUCTION`.
</Note>

### Supported providers and request body fields

| Provider                    | Path segment       | Request body fields                           |
| --------------------------- | ------------------ | --------------------------------------------- |
| AgLeader                    | `AgLeader`         | `privateKey`, `publicKey`                     |
| Climate FieldView           | `ClimateFieldView` | `apiKey`, `clientId`, `clientSecret`          |
| CNHI (AFS Connect - Legacy) | `CNHI`             | `clientId`, `clientSecret`, `subscriptionKey` |
| CNHI FieldOps               | `CNHIFieldOps`     | `clientId`, `clientSecret`, `subscriptionKey` |
| John Deere                  | `JohnDeere`        | `clientKey`, `clientSecret`                   |
| Trimble                     | `Trimble`          | `applicationName`, `clientId`, `clientSecret` |
| Raven Slingshot             | `RavenSlingshot`   | `apiKey`, `sharedSecret`                      |
| Stara                       | `Stara`            | `user`, `pwd`                                 |

<Warning>
  **CNHI**, **John Deere**, and **Trimble** require you to register `https://widget.withleaf.io` as a callback/redirect URL in your provider developer portal before Leaf Link can complete the OAuth flow.
</Warning>

***

### Example: John Deere

The examples below show the full CRUD lifecycle for John Deere. All other providers follow the same pattern — only the path segment and request body fields differ.

#### Create a John Deere app

`POST /app-keys/JohnDeere/{appName}/{clientEnvironment}`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST \
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -d '{
      "clientKey": "{clientKey}",
      "clientSecret": "{clientSecret}"
    }'
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION"
  headers = {
      "Authorization": f"Bearer {token}",
      "Content-Type": "application/json",
  }
  body = {
      "clientKey": client_key,
      "clientSecret": client_secret,
  }

  response = requests.post(url, headers=headers, json=body)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION",
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        clientKey,
        clientSecret,
      }),
    }
  );
  const app = await response.json();
  ```
</CodeGroup>

#### Get all John Deere apps

`GET /app-keys/JohnDeere`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET \
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere" \
    -H "Authorization: Bearer {token}"
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere"
  headers = {"Authorization": f"Bearer {token}"}

  response = requests.get(url, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere",
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const apps = await response.json();
  ```
</CodeGroup>

#### Get a John Deere app by name

`GET /app-keys/JohnDeere/{appName}/{clientEnvironment}`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET \
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION" \
    -H "Authorization: Bearer {token}"
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION"
  headers = {"Authorization": f"Bearer {token}"}

  response = requests.get(url, headers=headers)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION",
    { headers: { Authorization: `Bearer ${token}` } }
  );
  const app = await response.json();
  ```
</CodeGroup>

#### Update a John Deere app

`PUT /app-keys/JohnDeere/{appName}/{clientEnvironment}`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PUT \
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION" \
    -H "Authorization: Bearer {token}" \
    -H "Content-Type: application/json" \
    -d '{
      "clientKey": "{newClientKey}",
      "clientSecret": "{newClientSecret}"
    }'
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION"
  headers = {
      "Authorization": f"Bearer {token}",
      "Content-Type": "application/json",
  }
  body = {
      "clientKey": new_client_key,
      "clientSecret": new_client_secret,
  }

  response = requests.put(url, headers=headers, json=body)
  print(response.json())
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION",
    {
      method: "PUT",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        clientKey: newClientKey,
        clientSecret: newClientSecret,
      }),
    }
  );
  const updated = await response.json();
  ```
</CodeGroup>

#### Delete a John Deere app

`DELETE /app-keys/JohnDeere/{appName}/{clientEnvironment}`

<CodeGroup>
  ```bash cURL theme={null}
  curl -X DELETE \
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION" \
    -H "Authorization: Bearer {token}"
  ```

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

  url = "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION"
  headers = {"Authorization": f"Bearer {token}"}

  response = requests.delete(url, headers=headers)
  ```

  ```javascript JavaScript theme={null}
  await fetch(
    "https://api.withleaf.io/services/usermanagement/api/app-keys/JohnDeere/my-jd-app/PRODUCTION",
    {
      method: "DELETE",
      headers: { Authorization: `Bearer ${token}` },
    }
  );
  ```
</CodeGroup>

<Tip>
  Use the provider path matrix above when adapting these examples. Providers without `clientEnvironment` keep `appName` in the path, but omit the trailing environment segment.
</Tip>
