> 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.

# Versioning and stability

The Jelliu API evolves continuously. New endpoints, fields, events and enum values ship without a new API version, so the most important thing a client can do is tolerate additions. This page describes the stability contract and the signals Jelliu uses when something is on its way out.

## The current version

|                          |                                                                                    |
| ------------------------ | ---------------------------------------------------------------------------------- |
| Public surface           | **v1**                                                                             |
| OpenAPI document version | `1.0.0`                                                                            |
| Base URL                 | `https://api.jelliu.co`                                                            |
| Version in the URL       | None. Paths are `/api/agents`, `/api/campaigns`, and so on, with no `/v1` segment. |
| Version header           | None. You do not send or pin a version.                                            |

Every request is served by the current v1 surface.

## What counts as a compatible change

These changes are **additive**. They are made at any time, without a version bump:

* New endpoints.
* New **optional** request fields and query parameters.
* New fields in responses.
* New members in enumerations, such as a new call `outcome`, a new campaign category, a new error `code` or a new webhook event name.
* New webhook events being sent, including events already listed in the catalog as not yet sent.
* Changes to the wording of error `message` strings.

Build your client so that none of these can break it.

#### Ignore fields you do not recognize

Responses may include fields that are not documented. Do not use strict deserialization that fails on unknown properties, and do not re-send a whole object you received back to an update endpoint.

#### Treat enumerations as open

Add a default branch whenever you switch on `status`, `outcome`, `channel`, `category`, an error `code` or a webhook `event`. A value you have never seen should be logged and handled gracefully, not crash your integration.

#### Branch on codes, not messages

Error messages are written for people, some are in Spanish, and they can be reworded at any time. The `error.code` and HTTP status are the contract. See [Errors](/errors).

#### Treat webhook payload fields as optional

Read `data` defensively, and acknowledge events you do not handle with a `2xx` so they do not count as failed deliveries. See [Webhooks](/webhooks#payload).

**`Node.js`**

```javascript title="Node.js"
// Tolerant handling of an open enumeration and unknown fields.
function describeOutcome(call) {
  switch (call.outcome) {
    case 'sale_closed':
      return 'Won';
    case 'callback_scheduled':
      return 'Follow up';
    case 'rejected':
      return 'Lost';
    case null:
    case undefined:
      return 'Not analyzed yet';
    default:
      console.warn('Unrecognized outcome, treating as neutral:', call.outcome);
      return 'Other';
  }
}

// Only pick the fields you use; everything else is ignored.
const { id, status, outcome } = await (async () => {
  const res = await fetch('https://api.jelliu.co/api/calls?limit=1', {
    headers: { Authorization: `Bearer ${process.env.JELLIU_API_KEY}` },
  });
  const body = await res.json();
  return body.data?.calls?.[0] ?? {};
})();
console.log(id, status, describeOutcome({ outcome }));
```

**`Python`**

```python title="Python"
import logging
import os

import requests

KNOWN = {
    "sale_closed": "Won",
    "callback_scheduled": "Follow up",
    "rejected": "Lost",
}


def describe_outcome(outcome):
    if outcome is None:
        return "Not analyzed yet"
    if outcome not in KNOWN:
        logging.warning("Unrecognized outcome, treating as neutral: %s", outcome)
        return "Other"
    return KNOWN[outcome]


res = requests.get(
    "https://api.jelliu.co/api/calls",
    params={"limit": 1},
    headers={"Authorization": f"Bearer {os.environ['JELLIU_API_KEY']}"},
    timeout=30,
)
calls = (res.json().get("data") or {}).get("calls") or []
if calls:
    call = calls[0]
    # .get() everywhere: fields can be added, and optional ones can be absent.
    print(call.get("id"), call.get("status"), describe_outcome(call.get("outcome")))
```

## Deprecations

When an interface is being retired, Jelliu marks responses from it with standard HTTP headers:

| Header                                | Standard | Meaning                                                                                                            |
| ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `Deprecation`                         | RFC 9745 | The interface is deprecated. The value is the date it was deprecated, for example `@2026-06-01T00:00:00Z`.         |
| `Sunset`                              | RFC 8594 | The date after which the interface may stop working, as an HTTP date, for example `Mon, 01 Jun 2026 00:00:00 GMT`. |
| `Link` with `rel="successor-version"` | RFC 8288 | Where to move to.                                                                                                  |

For example, the legacy inbound CRM webhook route `POST /webhooks/integrations/{tenantId}/{integrationId}` answers with all three, pointing to its replacement `POST /webhooks/integrations/v2/{webhookUuid}`:

```http
HTTP/1.1 200 OK
Deprecation: @2026-06-01T00:00:00Z
Sunset: Mon, 01 Jun 2026 00:00:00 GMT
Link: </webhooks/integrations/v2/5f0c2a9e-1b7d-4e3a-9c6f-2d8e7a1b0c34>; rel="successor-version"
```

Some deprecations cannot be signalled with a response header, because the caller is Jelliu rather than you. The one currently in progress:

* **Legacy webhook signatures.** Outbound webhook deliveries still carry `X-Webhook-Signature` and `X-Webhook-Signature-V1` (HMAC of the body alone) next to `X-Webhook-Signature-V2`. The legacy headers will be removed. Verify `X-Webhook-Signature-V2` today. See [Verifying signatures](/webhooks#verifying-signatures).

### Detect deprecated calls automatically

Log a warning whenever a response carries a `Deprecation` or `Sunset` header, so a retirement shows up in your monitoring long before the sunset date.

**`cURL`**

```bash title="cURL"
curl -sS -D - -o /dev/null "https://api.jelliu.co/api/agents" \
  -H "Authorization: Bearer $JELLIU_API_KEY" | grep -iE '^(deprecation|sunset|link):'
```

**`Node.js`**

```javascript title="Node.js"
async function jelliu(path, init = {}) {
  const res = await fetch(`https://api.jelliu.co${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.JELLIU_API_KEY}`,
      'Content-Type': 'application/json',
      ...init.headers,
    },
  });

  const deprecation = res.headers.get('Deprecation');
  const sunset = res.headers.get('Sunset');
  if (deprecation || sunset) {
    console.warn('Jelliu deprecation', {
      path,
      deprecation,
      sunset,
      successor: res.headers.get('Link'),
    });
  }
  return res;
}
```

**`Python`**

```python title="Python"
import logging
import os

import requests

session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['JELLIU_API_KEY']}"


def warn_on_deprecation(response, *args, **kwargs):
    deprecation = response.headers.get("Deprecation")
    sunset = response.headers.get("Sunset")
    if deprecation or sunset:
        logging.warning(
            "Jelliu deprecation path=%s deprecation=%s sunset=%s successor=%s",
            response.request.path_url,
            deprecation,
            sunset,
            response.headers.get("Link"),
        )


session.hooks["response"].append(warn_on_deprecation)

res = session.get("https://api.jelliu.co/api/agents", timeout=30)
```

## Machine-readable reference

The OpenAPI document behind the [API Reference](/api-reference) is served by the API itself and regenerated on every deploy, so it always matches what is running:

| URL                                                             | Contents                                                                                                                                    |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `https://api.jelliu.co/openapi.public.yaml`                     | The integrator-facing reference. Provider callbacks, dashboard-only flows and the dashboard's session credential are removed. Use this one. |
| `https://api.jelliu.co/postman/Jelliu.postman_collection.json`  | A Postman collection.                                                                                                                       |
| `https://api.jelliu.co/postman/Jelliu.postman_environment.json` | The matching Postman environment.                                                                                                           |

All are public and cached for up to 5 minutes.

**`cURL`**

```bash title="cURL"
curl -sS https://api.jelliu.co/openapi.public.yaml -o jelliu-openapi.yaml
```

If you generate a client from the OpenAPI document, regenerate it periodically and keep the generator configured to allow unknown properties and unknown enum values. A generated client with closed enums turns every additive change into a runtime error.

## What to watch

* **[Changelog](/changelog).** Notable changes to the API and to this documentation.
* **Response headers.** `Deprecation` and `Sunset`, as above.
* **Webhook event catalog.** Events marked "Not yet" in [Webhooks](/webhooks#event-catalog) are accepted in subscriptions today and can start being delivered. Subscribe only to events you handle, or make your receiver ignore unknown ones.
* **Error codes.** New codes can appear. Map unknown `4xx` codes to a generic, non-retryable failure and unknown `5xx` codes to a retry with backoff. See [Errors](/errors#retrying).

## Related

#### [Changelog](/changelog)

Notable changes to the API and documentation.

#### [Errors](/errors)

Stable error codes and the retry rules.

#### [Webhooks](/webhooks)

Payloads, signatures and the event catalog.

#### [Security](/security)

Transport, authentication and signing.