---
url: https://talkjs.com/docs/REST_API
---

# Getting started

Authenticate and manage your chat from your backend.

Ask a question Copy for LLM [View as Markdown](/docs/REST_API.md)
Call the TalkJS REST API to manage messages, conversations and users from your backend. The API is REST-based, using HTTP and JSON. The API only accepts authenticated calls over HTTPS, and uses HTTP status codes for reporting results.

#### TalkJS API base URL

`https://api.talkjs.com`

## Authentication

The TalkJS REST API has read/write access to all data in your TalkJS account.
Therefore you need to use an Admin auth token that grants access to all data in your TalkJS account.

Since these tokens are so powerful, we strongly recommend generating single-use tokens.
Your server should generate a new token for each REST API request.
That means the tokens can use an extremely short expiry time, so that even if you accidentally leak a token, it will expire before anyone can exploit it.

**Never** expose your secret key in frontend code.

Anyone with a REST API token has admin access to your TalkJS account.
Your users **must only** call the REST API by asking your backend to do it for them.

Generating a REST API token that expires after 30 seconds:

**NodeJS**
```javascript
// Uses `jsonwebtoken`: https://www.npmjs.com/package/jsonwebtoken
import jwt from 'jsonwebtoken';

const encoded_jwt = jwt.sign({ tokenType: 'admin' }, '<SECRET_KEY>', {
  issuer: '<APP_ID>',
  expiresIn: '30s',
});
console.log(encoded_jwt);
```

**Python**
```python
# Uses `PyJWT`: https://pypi.org/project/PyJWT/
import jwt
import time

payload = {
  "tokenType": "admin",
  "iss": "<APP_ID>",
  "exp": time.time() + 30
}
encoded_jwt = jwt.encode(payload, "<SECRET_KEY>")
print(encoded_jwt)
```

**PHP**
```php
<?php
// Uses `lcobucci/jwt`: https://packagist.org/packages/lcobucci/jwt
use Lcobucci\JWT\Encoding\ChainedFormatter;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Token\Builder;
require 'vendor/autoload.php';

$tokenBuilder = new Builder(new JoseEncoder(), ChainedFormatter::default());
$algorithm = new Sha256();
$signingKey = InMemory::plainText("<SECRET_KEY>_GOES_HERE_REPLACING_THIS_TEXT");

$now = new DateTimeImmutable();
$token = $tokenBuilder
  ->withClaim('tokenType', 'admin')
  ->issuedBy('<APP_ID>')
  ->expiresAt($now->modify('+30 seconds'))
  ->getToken($algorithm, $signingKey)
  ->toString();
echo $token;
```

**Ruby**
```ruby
# Uses `jwt`: https://rubygems.org/gems/jwt
require 'jwt'

payload = {
  tokenType: 'admin',
  iss: '<APP_ID>',
  exp: Time.now.to_i + 30
}
token = JWT.encode payload,
  '<SECRET_KEY>',
  'HS256'
puts token
```

**Java**
```clike
// Uses `java-jwt`: https://mvnrepository.com/artifact/com.auth0/java-jwt
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.JWT;
import java.util.Date;

public class Main {
  public static void main(String[] args) {
    Algorithm algorithm = Algorithm.HMAC256("<SECRET_KEY>");
    String token = JWT.create()
      .withClaim("tokenType", "admin")
      .withIssuer("<APP_ID>")
      .withExpiresAt(new Date(System.currentTimeMillis() + 30 * 1000))
      .sign(algorithm);
    System.out.println(token);
  }
}
```

**C#**
```clike
// Uses `jose-jwt`: https://www.nuget.org/packages/jose-jwt/
using Jose;
using System;
using System.Collections.Generic;
using System.Text;

class Program
{
  static void Main(string[] args)
  {
    var epochSeconds = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
    var payload = new Dictionary<string, object>()
      {
        { "tokenType", "admin" },
        { "iss", "<APP_ID>" },
        { "exp", epochSeconds + 30 }
      };
    var secret = Encoding.UTF8.GetBytes("<SECRET_KEY>");
    string token = Jose.JWT.Encode(payload, secret, JwsAlgorithm.HS256);
    Console.WriteLine(token);
  }
}
```

**Go**
```go
package main

import (
  "fmt"
  "time"
)

import "github.com/golang-jwt/jwt/v5"

func main() {
  token := jwt.NewWithClaims(
    jwt.SigningMethodHS256,
    jwt.MapClaims{
      "tokenType": "admin",
      "iss": "<APP_ID>",
      "exp": time.Now().Add(10 * time.Minute).Unix(),
    })

  secret := []byte("<SECRET_KEY>")
  tokenString, err := token.SignedString(secret)
  fmt.Println(tokenString, err)
}
```

**Elixir**
```elixir
# Uses `joken`: https://hex.pm/packages/joken
# Requires json library eg `jason`: https://hex.pm/packages/jason

# config/config.exs
import Config
config :joken, default_signer: "<SECRET_KEY>"

# lib/talkjs_token.ex
defmodule TalkjsToken do
  use Joken.Config

  def token_config do
    default_claims(
      skip: [:aud, :jti],
      iss: "<APP_ID>",
      default_exp: 30
    )
    |> add_claim("tokenType", fn -> "admin" end)
  end
end

# lib/main.ex
token = TalkjsToken.generate_and_sign!(%{})
IO.inspect(token)
```

**Dart**
```dart
// Uses `dart_jsonwebtoken`: https://pub.dev/packages/dart_jsonwebtoken
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';

void main(List<String> arguments) {
  final jwt = JWT(
    {'tokenType': 'admin'},
    issuer: '<APP_ID>',
  );

  final encoded_jwt = jwt.sign(
    SecretKey('<SECRET_KEY>'),
    expiresIn: Duration(seconds: 30),
  );

  print(encoded_jwt);
}
```

Authentication is performed with the `Authorization` header.
Provide your token in the following format:

`Authorization: Bearer <YOUR_TOKEN>`

For example:

```javascript
fetch('https://api.talkjs.com/v1/<APP_ID>', {
  headers: {
    Authorization: 'Bearer ' + generateToken(),
  },
});
```

You can read more about the tokens used by TalkJS in our [authentication reference](/docs/Features/Security/Advanced_Authentication/#token-reference).

### Secret key authentication (legacy)

For legacy support, we also allow using your secret key as an auth token directly.
For example:

`Authorization: Bearer sk_live...`

However we now recommend that **all customers should generate single-user tokens** instead.
This is much safer than using your secret key directly.

If you leak your secret key, a malicious user can exploit it to create new tokens and access all your data, forever.
The only way to stop this is by rotating your secret key, causing downtime for your legitimate users.
In comparison, a leaked single-use token will probably expire before anyone can exploit it.

## Content type

Only requests with `Content-type: application/json` are accepted, except for HTTP GET requests, which have no content.

## Return values

TalkJS uses HTTP status codes to indicate whether the request was fine or not.
TalkJS **always** returns HTTP status 200 if a request was processed successfully.

Statuses 4xx mean that the user input wasn't correct, to be more precise:

- 400 means that the arguments passed weren't correct
- 401 means that the "Authorization" header with the token wasn't present or was incorrect.
- 404 means that the resource couldn't be located.
Very rarely, TalkJS may return status 5xx, indicating that something went wrong on the TalkJS servers. This may indicate a bug in TalkJS, but it might also be unexpected downtime. Treat this as you would a connection error; you can safely retry this operation.

The HTTP status code is the only way to verify whether a call was successful. Don't inspect the response body for determining this.

All API responses are JSON. Even calls that return no data have a `{}` response.

## Listing

Most of our resources have support for *listing*. You can list users, conversations or messages. They all share the same response structure and the same arguments. It accepts common parameters like `limit` and `startingAfter`.

## Limits

The limit parameter can be added to fetching endpoints to specify the number of results that should be returned, for example: `?limit=20`.

- When fetching [Conversations](/docs/REST_API/Conversations/#listing-all-conversations-in-the-application) `limit` must be a number between 1 and 30. The default limit is 10.
- When fetching [Users](/docs/REST_API/Users/#listing-all-users-in-the-application) or [Messages](/docs/REST_API/Messages/#listing-messages-from-a-conversation) `limit` must be a number between 1 and 100. The default limit is 10.

## Pagination

You can paginate through a list of results using the `startingAfter` cursor. `startingAfter` is
an object ID that identifies a place in the record list. For example, if you request 10 records and the
last record's ID is `c10`, then you can pass `startingAfter=c10` as an argument to get the next 10 results.

By default, all records are sorted in descending order based on their `createdAt` property, which
is a timestamp of a record's insertion date.

## Response

The response is a JSON object containing a field called `data` that keeps an array of requested resources.

## Rate limits

You can send a burst of up to 600 requests before receiving `HTTP 429 Too Many Requests` responses.
Over a sustained period, you can send a maximum of 9 requests per second, or 1 batch request per second.
These limits apply per app ID.

For more information, see the [rate limiting documentation](/docs/REST_API/Rate_Limits/).