Your AI Connector Docs

API des points d’entrée

Un point d’entrée est une règle de routage : « quand ceci se produit sur ce canal, transférez la conversation à cet agent ». Connecter un canal permet de recevoir des messages sur le compte et créer un agent vous donne quelqu’un capable de répondre, mais aucun des deux ne décide qui répond au premier message d’un inconnu. Les points d’entrée s’en chargent. Pour le produit lui-même, consultez le guide des points d’entrée.

Tous les exemples ci-dessous utilisent le format de requête ?apiKey= en cURL et l’en-tête X-API-Key en JavaScript et Python — les deux fonctionnent sur chaque point de terminaison.

Dans l’explorateur d’API. Chaque point de terminaison sur cette page figure dans la spécification OpenAPI publiée, vous pouvez donc parcourir ses champs exacts et exécuter des requêtes en direct dans l’explorateur d’API.


L’appel dont la plupart des intégrations ont besoin

Connectez un canal, créez un agent, puis pointez le canal vers l’agent :

curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "whatsapp", "agent_id": "ag7HkQ2ZpLxR3mNb" }'

C’est toute la configuration nécessaire pour que « cet agent réponde sur WhatsApp ». Tout le reste sur cette page concerne des règles plus spécifiques (mots-clés, commentaires, nouveaux abonnés), plusieurs numéros sur un même canal, et la lecture de la configuration actuelle.


Comment le routage est décidé

Lorsqu’un message arrive, la plateforme suit une échelle fixe et la première étape qui prend une décision l’emporte :

  1. Un humain a pris en charge la conversation — pas d’IA.
  2. Le contact est déjà assigné à un agent, manuellement ou parce qu’une conversation avec cet agent est en cours — le même agent la conserve. Les points d’entrée ne déplacent jamais une conversation existante ; pour transférer une discussion à un autre agent, assignez-la (dans l’application ou avec l’action Automatisations).
  3. Le contact répond à une diffusion — l’agent de la diffusion répond, ou personne si la diffusion n’en avait pas.
  4. Un point d’entrée spécifique correspond. Les règles de mots-clés l’emportent sur les règles de commentaires, qui l’emportent sur les règles d’abonnés. Entre deux règles de même type, la plus récemment mise à jour l’emporte.
  5. La valeur par défaut du canal sur lequel le message est arrivé. Une valeur par défaut limitée au numéro spécifique auquel le contact a écrit l’emporte sur la valeur par défaut à l’échelle du canal.
  6. Rien ne correspond — le message arrive dans la boîte de réception de votre équipe et aucun assistant ne répond.

Deux choses atténuent l’étape 6. Un compte avec exactement un agent actif et aucune valeur par défaut configurée pour le canal reçoit toujours cet agent comme répondant, donc un nouveau compte qui connecte WhatsApp et envoie un message de test ne reste pas sans réponse. Ce plancher ne s’applique jamais à un canal qui possède une règle de mot-clé (là, un message qui ne correspond à aucun mot-clé est délibérément laissé à un humain) et ne remplace jamais un canal que vous avez défini sur « personne » (voir Laisser un canal sans personne pour répondre).

Le fait que l’échelle soit active pour un compte est indiqué par GET /entry-points/routing-status. Elle est activée pour chaque compte aujourd’hui ; l’appel existe pour qu’une intégration puisse vérifier plutôt que de supposer.


L’objet Point d’entrée

{
  "id": "ep3KmQ8vTzXr5nWd",
  "type": "keyword",
  "channels": ["whatsapp", "instagram"],
  "agent_id": "ag7HkQ2ZpLxR3mNb",
  "enabled": true,
  "match_config": {
    "keywords": ["pricing", "quote"]
  },
  "first_response_mode": null,
  "first_response_exact_text": null,
  "public_comment_reply_exact_text": null,
  "created_at": 1700000000000,
  "last_modified_at": 1700000000000
}
Champ Description
id L’ID de la règle.
type L’un des types channel_default, keyword, instagram_comment, facebook_comment, instagram_follower. Voir Types de règles.
channels Les canaux couverts par la règle : whatsapp, whatsapp_web, instagram, instagram_private, messenger, telegram, sms, email, chat_widget, custom_channel, line, viber, tiktok, imessage, linkedin, skool. Les règles de commentaires utilisent instagram ou facebook.
agent_id L’agent vers lequel la règle achemine. Vide pour une valeur par défaut de canal délibérément définie sur « personne ».
enabled false pour une règle qui a été retirée. Les règles retirées sont de l’historique, pas des paramètres actifs, et les deux sont renvoyés par les points de terminaison de liste.
match_config Paramètres spécifiques au type — voir Types de règles. Vide pour une valeur par défaut de canal simple.
first_response_mode ai (par défaut) permet à l’agent d’écrire la première réponse ; exact_text envoie first_response_exact_text mot pour mot. Respecté sur les règles de commentaires aujourd’hui ; accepté et stocké sur les règles de mots-clés mais pas encore utilisé.
first_response_exact_text Le premier message privé fixe lorsque first_response_mode est exact_text. {{first_name}} est remplacé par le prénom de la personne, ou « là » lorsqu’il est inconnu.
public_comment_reply_exact_text Règles de commentaires uniquement : la réponse publique fixe sous le commentaire. Laisser vide ignore la réponse publique ; le message privé est tout de même envoyé.
created_at, last_modified_at Millisecondes depuis l’époque Unix.

Types de règles

type Se déclenche quand match_config
channel_default Un nouveau contact inconnu écrit sur l’un des channels. phone_numbers (optionnel) — limiter la valeur par défaut à un numéro connecté au lieu de tout le canal. Voir Un agent par numéro WhatsApp.
keyword Le premier message d’un nouveau contact est l’un des keywords. La correspondance ignore la casse et les espaces, et une erreur de frappe (« info svp » par rapport à INFO) est toujours résolue par l’IA sauf si vous définissez fuzzy_match: false — faites-le pour les codes promotionnels et les SKU où une erreur de frappe ne doit pas compter. Non appliqué sur sms ou imessage. keywords (au moins un, requis), fuzzy_match (par défaut true).
instagram_comment / facebook_comment Quelqu’un commente l’une de vos publications. channels doit inclure instagram ou facebook respectivement. keywords (vide signifie que chaque commentaire sur les publications surveillées compte), post_ids (vide signifie toutes les publications), delay_minutes (attendre avant l’envoi du message privé), reply_instructions (comment l’agent doit formuler sa réponse).
instagram_follower Quelqu’un s’abonne à votre compte Instagram. Nécessite la connexion Instagram (Personnel) — la connexion officielle aux messages privés Instagram ne peut pas voir les abonnés. reply_instructions (optionnel).

Une règle de mot-clé sur un canal sans canal par défaut fonctionne également comme une porte : les messages qui ne correspondent à aucun des mots-clés ne reçoivent aucune réponse automatique et arrivent simplement dans votre boîte de réception, même sur un compte avec un seul Agent.


Associer un canal à un Agent

PUT /entry-points/channel-defaults — désigne un Agent comme répondant pour les nouveaux contacts sur un canal. Tout autre Agent actuellement défini comme défaut pour ce canal est retiré lors du même appel, de sorte qu’un canal a toujours exactement un répondant. Définir l’Agent qui est déjà par défaut ne change rien.

Champ Requis Description
channel Oui Le canal, par exemple whatsapp, whatsapp_web, instagram, messenger, telegram, sms, email, chat_widget ou custom_channel.
agent_id Oui L’Agent qui doit répondre. Doit appartenir à votre compte.
phone_number Non Limite le défaut à l’un de vos numéros connectés sur ce canal (E.164 avec le + initial, exactement tel qu’il apparaît sous les numéros connectés). Laisse le défaut à l’échelle du canal intact. Voir Un Agent par numéro WhatsApp.

cURL

curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "channel": "instagram", "agent_id": "ag7HkQ2ZpLxR3mNb" }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/entry-points/channel-defaults", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ channel: "instagram", agent_id: "ag7HkQ2ZpLxR3mNb" }),
});
const data = await res.json();

Python

import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"channel": "instagram", "agent_id": "ag7HkQ2ZpLxR3mNb"},
)
data = res.json()

Réponse

{
  "success": true,
  "entry_point_id": "ep3KmQ8vTzXr5nWd",
  "disabled_entry_point_ids": ["epPrevious1234"]
}

entry_point_id est la règle désormais en vigueur ; disabled_entry_point_ids liste toutes les règles retirées pour lui faire de la place (vide lorsqu’il n’y avait rien à remplacer). Seuls les contacts à qui vous n’avez jamais parlé sont affectés — toute personne déjà en conversation avec un Agent conserve cet Agent.

Une 400 signifie que channel ou agent_id est manquant, que l’Agent appartient à un autre compte, ou que phone_number n’est pas l’un de vos numéros connectés.


Voir qui répond à chaque canal

GET /entry-points/channel-defaults — chaque canal par défaut sur le compte, du plus récent au plus ancien, y compris ceux retirés (enabled: false) et un canal délibérément réglé sur personne (agent_id: ""). Filtrez vous-même sur enabled pour obtenir la situation actuelle.

cURL

curl "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/entry-points/channel-defaults", {
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();

Python

import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()

Réponse

{
  "success": true,
  "entry_points": [
    {
      "id": "ep3KmQ8vTzXr5nWd",
      "type": "channel_default",
      "channels": ["whatsapp"],
      "agent_id": "ag7HkQ2ZpLxR3mNb",
      "enabled": true,
      "match_config": {},
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    },
    {
      "id": "epAEnhHoozpoGVze",
      "type": "channel_default",
      "channels": ["whatsapp"],
      "agent_id": "agRotterdamBranch",
      "enabled": true,
      "match_config": { "phone_numbers": ["+31685101091"] },
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    }
  ]
}

Il s’agit de la lecture à l’échelle du compte. Lister les règles d’un seul Agent avec GET /agents/{agentId}/entry-points ne peut pas montrer un canal réglé sur personne, car cette règle n’appartient à aucun Agent.


Laisser un canal sans répondant

DELETE /entry-points/channel-defaults?channel=instagram — retire le défaut à l’échelle du canal pour un canal donné. Le canal est nommé en tant que paramètre de requête, pas dans un corps. Ajoutez &phone_number=%2B31685101091 pour effacer uniquement le défaut de ce numéro et laisser le numéro revenir à celui qui répond au canal.

cURL

curl -X DELETE "https://api.youraiconnector.com/v1/entry-points/channel-defaults?channel=instagram&apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch(
  "https://api.youraiconnector.com/v1/entry-points/channel-defaults?channel=instagram",
  { method: "DELETE", headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();

Python

import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/entry-points/channel-defaults",
    params={"channel": "instagram"},
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()

Réponse

{ "success": true, "disabled_entry_point_ids": ["ep3KmQ8vTzXr5nWd"] }

Sans danger à répéter : effacer un canal qui n’a pas de défaut est une 200 avec une liste vide. Effacer signifie annuler, pas rendre silencieux — sur un compte avec exactement un Agent actif, un canal non configuré revient toujours à cet Agent. Pour garder l’IA totalement hors d’un canal, choisissez Personne ne répond pour celui-ci dans le panneau Qui répond aux nouvelles conversations de l’application (cela écrit un défaut explicite “personne” que le repli ne remplace jamais), ou mettez l’Agent en pause avec PATCH /agents/{agentId}/active.


Un Agent par numéro WhatsApp

Le routage se fait par canal par défaut : tous vos numéros WhatsApp partagent un seul répondant. Avec deux numéros ou plus connectés sur WhatsApp Business ou WhatsApp Web, un défaut peut être limité à un seul numéro, afin qu’une entreprise avec un numéro par succursale ou par marque puisse attribuer à chacun son propre Agent au sein d’un même compte.

Envoyez phone_number avec l’appel set :

curl -X PUT "https://api.youraiconnector.com/v1/entry-points/channel-defaults?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp_web",
    "agent_id": "agRotterdamBranch",
    "phone_number": "+31685101091"
  }'
  • Le numéro doit être l’un de vos numéros connectés sur ce canal, écrit tel qu’il apparaît sous les numéros connectés (E.164 avec le +) ; toute autre valeur est une 400.
  • La règle est enregistrée comme valeur par défaut du canal avec match_config.phone_numbers: ["+31685101091"]. Un message arrivant sur ce numéro est dirigé vers son Agent ; tous les autres numéros continuent de suivre la valeur par défaut du canal.
  • Définir ou effacer la valeur par défaut du canal n’affecte pas les règles spécifiques aux numéros, et vice versa. Effacez la règle propre à un numéro avec DELETE /entry-points/channel-defaults?channel=whatsapp_web&phone_number=%2B31685101091.
  • Les réponses sont toujours envoyées depuis le numéro auquel le contact a écrit, afin que le contact continue de communiquer avec le même numéro et le même Agent.

Ajouter une règle plus spécifique

POST /agents/{agentId}/entry-points — crée une règle de mot-clé, de commentaire ou d’abonné (ou une valeur par défaut de canal, bien que PUT /entry-points/channel-defaults soit préférable pour cela car il retire l’ancien répondant pour vous). L’Agent dans le chemin est toujours prioritaire : une règle ne peut jamais être créée pour un Agent différent de celui présent dans l’URL.

Champ Requis Description
type Oui keyword, instagram_comment, facebook_comment, instagram_follower ou channel_default.
channels Oui Une liste non vide des canaux couverts par la règle. Une règle de commentaire doit lister son propre canal (instagram ou facebook).
match_config Dépend du type Voir Types de règles. Une règle de mot-clé nécessite au moins une entrée dans keywords.
enabled Non Par défaut true.
first_response_mode, first_response_exact_text, public_comment_reply_exact_text Non Les paramètres de première réponse décrits dans L’objet Point d’entrée.

cURL

curl -X POST "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "keyword",
    "channels": ["whatsapp", "instagram"],
    "match_config": { "keywords": ["pricing", "quote"] }
  }'

JavaScript

const res = await fetch(
  "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
  {
    method: "POST",
    headers: {
      "X-API-Key": "YOUR_API_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      type: "keyword",
      channels: ["whatsapp", "instagram"],
      match_config: { keywords: ["pricing", "quote"] },
    }),
  }
);
const data = await res.json();

Python

import requests

res = requests.post(
    "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "type": "keyword",
        "channels": ["whatsapp", "instagram"],
        "match_config": {"keywords": ["pricing", "quote"]},
    },
)
data = res.json()

Réponse (201)

{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }

Une règle de commentaire vers message privé qui ne réagit qu’aux commentaires contenant “LINK” sur deux publications spécifiques, attend deux minutes et envoie un premier message fixe :

{
  "type": "instagram_comment",
  "channels": ["instagram"],
  "match_config": {
    "keywords": ["LINK"],
    "post_ids": ["17895695668004550", "17841400008460056"],
    "delay_minutes": 2
  },
  "first_response_mode": "exact_text",
  "first_response_exact_text": "Hi {{first_name}}, here is the link you asked for: https://example.com/guide",
  "public_comment_reply_exact_text": "Sent you a DM!"
}

Laissez keywords vide pour envoyer un message privé à tous ceux qui commentent les publications surveillées, et post_ids vide pour surveiller toutes les publications. Une 400 indique ce qui ne va pas : un type inconnu, un channels vide, une règle de mot-clé sans mots-clés, ou une règle de commentaire qui ne liste pas son propre canal.


Lister les règles d’un Agent

GET /agents/{agentId}/entry-points — les règles qui envoient des conversations à cet Agent, de la plus récente à la plus ancienne : ses valeurs par défaut de canal, ses règles de mot-clé, ses règles de commentaire et ses règles d’abonné. Les règles retirées apparaissent également, avec enabled: false.

cURL

curl "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points?apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch(
  "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
  { headers: { "X-API-Key": "YOUR_API_KEY" } }
);
const data = await res.json();

Python

import requests

res = requests.get(
    "https://api.youraiconnector.com/v1/agents/ag7HkQ2ZpLxR3mNb/entry-points",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()

Réponse

{
  "success": true,
  "entry_points": [
    {
      "id": "ep3KmQ8vTzXr5nWd",
      "type": "keyword",
      "channels": ["whatsapp", "instagram"],
      "agent_id": "ag7HkQ2ZpLxR3mNb",
      "enabled": true,
      "match_config": { "keywords": ["pricing", "quote"] },
      "created_at": 1700000000000,
      "last_modified_at": 1700000000000
    }
  ]
}

Modifier une règle

PUT /entry-points/{entryPointId} — modifie une règle. Envoyez uniquement les champs que vous modifiez ; les paramètres imbriqués peuvent être traités feuille par feuille avec une clé pointée telle que "match_config.keywords". Chaque fois que la modification touche type, channels ou match_config, la règle entière est revérifiée, de sorte qu’une modification partielle ne peut jamais laisser une règle inutilisable (le passage de type à keyword sans fournir de mots-clés est rejeté). L’envoi de agent_id transfère la règle à un autre de vos Agents ; un champ vide est rejeté. Les champs de propriété et d’identité sont ignorés.

cURL

curl -X PUT "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd?apiKey=YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "match_config": { "keywords": ["pricing", "quote", "demo"] } }'

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd", {
  method: "PUT",
  headers: {
    "X-API-Key": "YOUR_API_KEY",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ match_config: { keywords: ["pricing", "quote", "demo"] } }),
});
const data = await res.json();

Python

import requests

res = requests.put(
    "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"match_config": {"keywords": ["pricing", "quote", "demo"]}},
)
data = res.json()

Réponse

{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }

Autres modifications courantes : { "enabled": false } retire une règle sans la supprimer, et { "agent_id": "agOtherAgent" } la déplace vers un autre Agent. Un corps vide renvoie 400 avec "No fields to update".


Supprimer une règle

DELETE /entry-points/{entryPointId} — supprime la règle définitivement. Rien d’autre ne fait référence à un Point d’entrée, il n’y a donc rien à détacher au préalable.

cURL

curl -X DELETE "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd?apiKey=YOUR_API_KEY"

JavaScript

const res = await fetch("https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd", {
  method: "DELETE",
  headers: { "X-API-Key": "YOUR_API_KEY" },
});
const data = await res.json();

Python

import requests

res = requests.delete(
    "https://api.youraiconnector.com/v1/entry-points/ep3KmQ8vTzXr5nWd",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
data = res.json()

Réponse

{ "success": true, "entry_point_id": "ep3KmQ8vTzXr5nWd" }

Pour empêcher une règle de se déclencher tout en la conservant, définissez enabled sur false à la place. Les valeurs par défaut des canaux, en particulier, sont normalement retirées plutôt que supprimées, ce que fait DELETE /entry-points/channel-defaults.


Vérifier que le routage est actif

GET /entry-points/routing-status — renvoie si la hiérarchie des points d’entrée décide qui répond sur ce compte. Lisible avec un accès en lecture, afin qu’un coéquipier voie la même réponse que le propriétaire.

curl "https://api.youraiconnector.com/v1/entry-points/routing-status?apiKey=YOUR_API_KEY"
{ "success": true, "cutover_enabled": true }

C’est true sur chaque compte aujourd’hui. L’appel est conservé afin qu’une intégration puisse vérifier avant d’informer quelqu’un que son changement de routage est actif, plutôt que de le supposer.


Les anciens appels basés sur les campagnes

Deux points de terminaison datant d’avant les Agents fonctionnent toujours pour les comptes organisés autour de campagnes. Les nouvelles intégrations doivent utiliser les appels de paramètres par défaut des canaux ci-dessus.

  • PUT /channel-routing/{channel} avec { "campaignId": "cp5NbV8xQrT2wYzA" } — nomme une campagne, et l’Agent de cette campagne devient celui qui répond pour le canal. { "campaignId": null } libère le canal. Une campagne sortante uniquement est rejetée car elle n’a aucun comportement entrant à offrir.
  • POST /channel-routing/clear avec { "channels": ["whatsapp", "instagram"] } — libère plusieurs canaux de l’Agent qui y répond, généralement avant de les rediriger ailleurs. La réponse liste released_channels, ceux qui avaient effectivement un répondant.

Les deux réinitialisent plutôt que de mettre en sourdine : sur un compte avec exactement un Agent actif, un canal libéré revient toujours à cet Agent.


Erreurs de l’API des points d’entrée

Les points de terminaison des points d’entrée renvoient l’enveloppe d’erreur standard :

{
  "success": false,
  "error": "Entry point not found"
}
Statut Quand cela se produit sur un point de terminaison de point d’entrée
400 Un champ est manquant ou la règle serait inutilisable : pas de channel ou agent_id lors d’un appel de définition, un type inconnu, un channels vide, une règle de mot-clé sans mots-clés, une règle de commentaire qui ne liste pas son propre canal, un agent_id vide lors d’une mise à jour, un corps de mise à jour vide, ou un phone_number qui n’est pas l’un de vos numéros connectés.
403 La clé ou le membre de l’équipe ne peut peut-être pas modifier le routage. Les écritures nécessitent des droits de modification sur les campagnes ; les lectures de liste et de statut nécessitent des droits de lecture.
404 Le point d’entrée ou l’Agent est introuvable — soit il n’existe pas, soit il appartient à un autre compte.

Les codes partagés que chaque point de terminaison peut renvoyer — 401, 403 (votre forfait n’inclut pas l’accès à l’API), 429 (limite de débit) et 500 — sont répertoriés avec des conseils de nouvelle tentative dans Erreurs et pagination.


Étapes suivantes