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

# List integrations

GET https://api.jelliu.co/api/integrations

Returns the workspace's integrations, newest first (`created_at` descending). Deleted
integrations are never included. Use `GET /api/integrations/{integrationId}` for the full
record (webhook secret, OAuth metadata, discovered schema); list rows carry only the display
columns documented on `Integration`.

**Pagination.** Offset-based. The response carries no total and no next-page marker: request the
next page with `offset` = `offset` + `limit` until a page comes back with fewer than `limit` rows.

**Consistency.** The first four pages (offsets that are exact multiples of `limit`) are cached for
up to 5 minutes and cleared on every write to an integration; other offsets are read live.

**Access**

* **Required scope:** `read` (or `write`). Any workspace member can call it.
* **Rate limit:** General API — 120 to 600 requests/min per workspace depending on plan. See [Rate limits](/rate-limits).
* **Plan:** Available on every plan.

Reference: https://developer.jelliu.co/api-reference/integrations/get-integrations

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Request

### Query parameters

- `limit` (integer, optional, default: 50) — Page size. Values outside 1–100 or non-integers are rejected with `400`, not clamped.
- `offset` (integer, optional, default: 0) — Number of integrations to skip. Values outside 0–100000 are rejected with `400`.

## Response

### 200

One page of integrations, newest first. Credentials are always the literal `***`.

- `data` (list of object, required)
  - `id` (string, optional) — Unique identifier.
  - `tenant_id` (string, optional) — Workspace that owns the integration.
  - `provider` (enum, optional) — External system. One live integration per provider per workspace. Only `webhook` has a built-in adapter; the CRM values remain for OAuth connections (Kommo powers the agent CRM tools) and for integrations created before CRMs moved to the app catalogue. Integrations created through the OAuth flow can also carry `calcom`.
    - Allowed values: `hubspot`, `salesforce`, `zoho`, `pipedrive`, `freshsales`, `airtable`, `google_sheets`, `webhook`, `dynamics365`, `zendesk`, `servicenow`, `kommo`, `monday`, `bitrix24`, `intercom`, `notion`
  - `label` (string, optional, nullable) — Display name, or `null`.
  - `auth_method` (enum, optional) — How Jelliu authenticates to the provider: `credentials` (JSON supplied at creation, the default), `oauth2` (tokens from the OAuth flow) or `api_key`.
    - Allowed values: `credentials`, `oauth2`, `api_key`
  - `status` (enum, optional) — Connection state: `active`, `paused` (set by you), `error` (the last test or token refresh failed — see `last_error`) or `pending_auth` (not verified yet, or the provider permanently rejected the refresh token and the user must reconnect). Only `active` and `paused` can be set with `PATCH`.
    - Allowed values: `active`, `paused`, `error`, `pending_auth`
  - `credentials` (string, optional) — Always the literal `***`. Stored credentials are never returned.
  - `contact_count` (integer, optional) — Contacts imported through this integration's inbound webhook so far.
  - `last_synced_at` (datetime, optional, nullable) — Last time the inbound webhook received a payload, or `null`.
  - `last_error` (string, optional, nullable) — Reason for the last failure, shown next to `status` `error`; `null` once a test or refresh succeeds. May be provider or product text in Spanish.
  - `field_mapping` (map from string to any, optional) — Source field name → Jelliu contact field (`phoneNumber`, `email`, `whatsappNumber`, `name`). `{}` when unset.
  - `campaign_id` (string, optional, nullable) — Campaign that inbound contacts are added to, or `null`.
  - `oauth_expires_at` (datetime, optional, nullable) — When the current OAuth access token expires; `null` for non-OAuth or non-expiring tokens.
  - `oauth_scopes` (string, optional, nullable) — Scopes granted by the provider, as the single string the provider returned, or `null`.
  - `inbound_webhook_uuid` (string, optional) — Opaque id of the inbound URL `/webhooks/integrations/v2/{inbound_webhook_uuid}`. Every integration has one; only `webhook` integrations accept pushes on it.
  - `created_at` (datetime, optional) — When the integration was created.
  - `updated_at` (datetime, optional) — Last change to the record, including status and sync updates.
  - `deleted_at` (datetime, optional, nullable) — Always `null` in responses; deleted integrations are not returned.

## Errors

### 400 Bad Request Error

`limit` or `offset` is not an integer in range.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `PAYLOAD_TOO_LARGE`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields); * for a rejected multipart upload (file in an unexpected field, too many files), a list with the upload error `code` (for example `LIMIT_UNEXPECTED_FILE`) and the `field` name. Absent when the value was rejected outside the route's own validation: a malformed id, number or date the database could not read, or a JSON body that does not parse.
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps), `tier` alone (feature gates), or `tier` = `none` with `kind` = `subscription_required` when the workspace has no active subscription. See [Errors](/errors).

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `PAYLOAD_TOO_LARGE`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields); * for a rejected multipart upload (file in an unexpected field, too many files), a list with the upload error `code` (for example `LIMIT_UNEXPECTED_FILE`) and the `field` name. Absent when the value was rejected outside the route's own validation: a malformed id, number or date the database could not read, or a JSON body that does not parse.
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps), `tier` alone (feature gates), or `tier` = `none` with `kind` = `subscription_required` when the workspace has no active subscription. See [Errors](/errors).

### 403 Forbidden Error

The API key has neither `read` nor `write`, or the workspace is suspended.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `PAYLOAD_TOO_LARGE`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields); * for a rejected multipart upload (file in an unexpected field, too many files), a list with the upload error `code` (for example `LIMIT_UNEXPECTED_FILE`) and the `field` name. Absent when the value was rejected outside the route's own validation: a malformed id, number or date the database could not read, or a JSON body that does not parse.
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps), `tier` alone (feature gates), or `tier` = `none` with `kind` = `subscription_required` when the workspace has no active subscription. See [Errors](/errors).

### 429 Too Many Requests Error

General API limit exceeded.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `PAYLOAD_TOO_LARGE`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields); * for a rejected multipart upload (file in an unexpected field, too many files), a list with the upload error `code` (for example `LIMIT_UNEXPECTED_FILE`) and the `field` name. Absent when the value was rejected outside the route's own validation: a malformed id, number or date the database could not read, or a JSON body that does not parse.
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps), `tier` alone (feature gates), or `tier` = `none` with `kind` = `subscription_required` when the workspace has no active subscription. See [Errors](/errors).

## Examples

**Response**

```json
{
  "data": [
    {
      "id": "3d9f1b2a-6c4e-4f8a-9b1d-2e7c5a0f8d13",
      "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
      "provider": "kommo",
      "label": "Kommo ventas Bogotá",
      "auth_method": "oauth2",
      "status": "active",
      "credentials": "***",
      "contact_count": 0,
      "last_synced_at": null,
      "last_error": null,
      "field_mapping": {},
      "campaign_id": null,
      "oauth_expires_at": "2026-09-16T02:14:09.000Z",
      "oauth_scopes": null,
      "inbound_webhook_uuid": "5b2e8c71-0d4a-4e93-b6f2-9a1c3d7e0b44",
      "created_at": "2026-09-02T14:21:37.000Z",
      "updated_at": "2026-09-15T02:14:09.000Z",
      "deleted_at": null
    },
    {
      "id": "c71e4a09-2f8b-4d63-8e15-b90d3a6c2f71",
      "tenant_id": "8a41c7e2-3b5d-4f09-a6e1-7d2c9b0f4e58",
      "provider": "webhook",
      "label": "Formulario web",
      "auth_method": "credentials",
      "status": "active",
      "credentials": "***",
      "contact_count": 412,
      "last_synced_at": "2026-09-14T22:03:51.000Z",
      "last_error": null,
      "field_mapping": {
        "correo": "email",
        "nombre": "name",
        "telefono": "phoneNumber"
      },
      "campaign_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
      "oauth_expires_at": null,
      "oauth_scopes": null,
      "inbound_webhook_uuid": "e04f7b3a-91c2-4d58-a3e6-1f8b2c9d7a05",
      "created_at": "2026-08-19T16:45:02.000Z",
      "updated_at": "2026-09-14T22:03:51.000Z",
      "deleted_at": null
    }
  ]
}
```

**SDK Code**

```python Integrations_getIntegrations_example
import requests

url = "https://api.jelliu.co/api/integrations"

querystring = {"limit":"20","offset":"0"}

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers, params=querystring)

print(response.json())
```

```javascript Integrations_getIntegrations_example
const url = 'https://api.jelliu.co/api/integrations?limit=20&offset=0';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Integrations_getIntegrations_example
package main

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

func main() {

	url := "https://api.jelliu.co/api/integrations?limit=20&offset=0"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

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

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

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

}
```

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

url = URI("https://api.jelliu.co/api/integrations?limit=20&offset=0")

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

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

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

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

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/integrations?limit=20&offset=0")
  .header("Authorization", "Bearer <token>")
  .asString();
```

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

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/integrations?limit=20&offset=0', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

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

```csharp Integrations_getIntegrations_example
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/integrations?limit=20&offset=0");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Integrations_getIntegrations_example
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/integrations?limit=20&offset=0")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

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()
```