> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.trykeet.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.trykeet.com/_mcp/server.

## Add Keet Link to Your Product

> **Tip**
>
> If you get stuck at any point, email for help at [zack@trykeet.com](mailto:zack@trykeet.com). If you don't get a response in 10 minutes, we'll
> buy you coffee.

Keet link uses a series of token exchanges to authenticate your users accounts.

**Step 1**: Get a `linkToken` so that you can initialize a link session for your users

**Step 2**: Make Keet Link appear in your frontend using the `linkToken`

**Step 3**: Swap the `publicToken`, returned by `keetLink` for an `accountToken`

**Step 4**: Use the `accountToken` for future requests to authenticated sessions.

### Step 1: Get a Link Token (Server side)

In your backend, set up a POST request to initialize a Keet Link session and get a `linkToken` from this URL: `https://api.trykeet.com/v1/link/token`

#### Supported Integrations

* `Instagram`
* `LinkedIn`
* `Amazon`
* `Amazon Business`
* `Amazon Seller`
* `X`
* `Venmo`
* `Facebook`
* `WhatsApp`
* `Uber`

### Request

POST [https://api.trykeet.com/v1/link/token](https://api.trykeet.com/v1/link/token)

```curl
curl -X POST https://api.trykeet.com/v1/link/token \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "linkConfig": {
    "endUserId": "<userId>",
    "integration": "Instagram",
    "companyLogoUri": "https://example.com/logo.png"
  }
}'
```

```typescript
import { KeetClient, Keet } from "@keet-tech/keet-node-client";

const client = new KeetClient({ token: "YOUR_TOKEN", accountToken: "YOUR_ACCOUNT_TOKEN" });
await client.link.createLinkToken({
    linkConfig: {
        endUserId: "<userId>",
        integration: Keet.OfferedIntegrations.Instagram,
        companyLogoUri: "https://example.com/logo.png"
    }
});

```

```python
import requests

url = "https://api.trykeet.com/v1/link/token"

payload = { "linkConfig": {
        "endUserId": "<userId>",
        "integration": "Instagram",
        "companyLogoUri": "https://example.com/logo.png"
    } }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.trykeet.com/v1/link/token"

	payload := strings.NewReader("{\n  \"linkConfig\": {\n    \"endUserId\": \"<userId>\",\n    \"integration\": \"Instagram\",\n    \"companyLogoUri\": \"https://example.com/logo.png\"\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.trykeet.com/v1/link/token")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"linkConfig\": {\n    \"endUserId\": \"<userId>\",\n    \"integration\": \"Instagram\",\n    \"companyLogoUri\": \"https://example.com/logo.png\"\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.trykeet.com/v1/link/token")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"linkConfig\": {\n    \"endUserId\": \"<userId>\",\n    \"integration\": \"Instagram\",\n    \"companyLogoUri\": \"https://example.com/logo.png\"\n  }\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.trykeet.com/v1/link/token', [
  'body' => '{
  "linkConfig": {
    "endUserId": "<userId>",
    "integration": "Instagram",
    "companyLogoUri": "https://example.com/logo.png"
  }
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.trykeet.com/v1/link/token");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"linkConfig\": {\n    \"endUserId\": \"<userId>\",\n    \"integration\": \"Instagram\",\n    \"companyLogoUri\": \"https://example.com/logo.png\"\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["linkConfig": [
    "endUserId": "<userId>",
    "integration": "Instagram",
    "companyLogoUri": "https://example.com/logo.png"
  ]] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.trykeet.com/v1/link/token")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: \{ (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Response (200)

```json
{
  "status": "ok",
  "linkToken": "<token>"
}
```

You can now pass this public `linkToken` back to your client to initialize the link component.

### Step 2: Make the Keet Link Appear (Client side)

#### React

1. Install the link sdk: `npm install @keet-tech/react-keet-link`

2. Use the `linkToken` from **[Step 1](#step-1-get-a-link-token)** to open Keet link

```tsx
  import { useKeetLink } from "@keet-tech/react-keet-link"
  import { useEffect, useState } from "react"

  export default function App() \{
    const onSuccess = (publicToken: string) => {
      /* send a request to your backend
      to exchange this for an account token */
    }

    const onValidationError = (errors: any) => {
      console.error("An error from Keet Link occured: ", errors)
    }

    const { open, isReady } = useKeetLink({
      linkToken: "ADD YOUR LINK TOKEN -- SEE STEP 1",
      onSuccess,
      onValidationError
    })



    return (
      <button disabled={!isReady} onClick={open}>Open Link</button>
    )
  }

  export default App;
```

#### HTML/JS

```html
<button id="open-link-button">Start linking</button>
<script src="https://cdn.trykeet.com/initialize.js"></script>
<script type="text/javascript">
  const button = document.getElementById("open-link-button");
  button.disabled = true;

function onSuccess(public_token) {
  // Send public_token to server (Step 3)
}

const linkConfig = \{
  // Replace ADD_GENERATED_LINK_TOKEN with the token retrieved from your backend (Step 1)
  linkToken: "ADD_GENERATED_LINK_TOKEN",
  onSuccess: (public_token) => onSuccess(public_token),
  onReady: () => (button.disabled = false),
}

KeetLink.initialize(linkConfig);

button.addEventListener("click", function () {
  KeetLink.openLink(linkConfig);
});
</script>
```

#### React Native (Mobile)

1. Install the link sdk: \
   &#x20;`npm install --save @keet-tech/react-native-keet-link react-native-svg`

2. Use the `linkToken` from **[Step 1](#step-1-get-a-link-token)** to open Keet link:

```tsx
import KeetLink, { LinkError } from "@keet-tech/react-native-keet-link"

export default function App() \{
  const onSuccess = (publicToken: string) => {
    /* send a request to your backend
    to exchange this for an account token */
  }

  const onError = (errors: LinkError[]) => {
    console.error("An error from Keet Link occured: ", errors)
  }

  return (
    <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
      <KeetLink linkToken={linkToken} onSuccess={onSuccess} onError={onError}>
        <Text style={{ color: "blue" }}>Open Link</Text>
      </KeetLink>
    </View>
  )
}
```

### Step 3: Swap `publicToken` for `accountToken` (Server Side)

In your backend, create a request to exchange the short-lived `publicToken` for a permanent `accountToken`.

\<### Request

POST [https://api.trykeet.com/v1/linked-accounts/token](https://api.trykeet.com/v1/linked-accounts/token)

```curl
curl -X POST https://api.trykeet.com/v1/linked-accounts/token \
     -H "Authorization: Bearer <token>" \
     -H "Content-Type: application/json" \
     -d '{
  "publicToken": "..."
}'
```

```typescript
import { KeetClient } from "@keet-tech/keet-node-client";

const client = new KeetClient({ token: "YOUR_TOKEN", accountToken: "YOUR_ACCOUNT_TOKEN" });
await client.linkedAccounts.getToken({
    publicToken: "..."
});

```

```python
import requests

url = "https://api.trykeet.com/v1/linked-accounts/token"

payload = { "publicToken": "..." }
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.trykeet.com/v1/linked-accounts/token"

	payload := strings.NewReader("{\n  \"publicToken\": \"...\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.trykeet.com/v1/linked-accounts/token")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"publicToken\": \"...\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.trykeet.com/v1/linked-accounts/token")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"publicToken\": \"...\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.trykeet.com/v1/linked-accounts/token', [
  'body' => '{
  "publicToken": "..."
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.trykeet.com/v1/linked-accounts/token");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"publicToken\": \"...\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

````swift
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = ["publicToken": "..."] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.trykeet.com/v1/linked-accounts/token")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: \{ (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```\<### Response (200)

```json
{
  "status": "ok",
  "accountToken": "acc_..."
}
````

> **Note**
>
> Store this account token someplace secure. It is used to make all requests to this end users authenticated sessions.

### You're done!

Use the `accountToken` to make all future requests on behalf of the user to every integration they use.

## Check out how to use your integrations

#### [Integration Overview](/overview/quick-start)