Cette page décrit les règles que suit chaque endpoint de l'API d'intégration. Les pages des endpoints ne mentionnent que les cas où un endpoint s'en écarte.
Requêtes
L'API est de type RPC sur HTTP POST, et non REST. Les endpoints sont des requêtes POST vers un chemin
qui nomme l'action, par exemple :
| Chemin | Action |
|---|---|
/api/v3/conversation/find |
lister les conversations |
/api/v3/conversation/get |
lire une conversation |
/api/v3/conversation/delete |
supprimer une conversation |
Il n'existe aucun endpoint PUT, PATCH ou DELETE ni aucun identifiant de ressource dans le chemin ;
l'identifiant d'un enregistrement est un paramètre. Chaque endpoint accepte POST. Deux endpoints en lecture seule
sans paramètres, solde de crédits et
version, répondent aussi à GET pour la compatibilité avec
les anciens clients ; utilisez POST dans le nouveau code.
L'URL de base est :
https://ayeto.ai/api/v3
En-têtes
| En-tête | Obligatoire | Description |
|---|---|---|
uni-api-key |
oui | Votre clé API, voir Authentification. |
Content-Type |
oui, avec un corps | application/json. |
language |
non | Langue de la requête : EN, CZ ou FR (insensible à la casse). Par défaut EN. |
theme |
non | light ou dark. Par défaut light. Transmis au modèle dans le chat comme indication sur l'endroit où la réponse est affichée. |
L'en-tête language indique au modèle dans le chat la langue dans laquelle l'utilisateur
écrit et sélectionne la langue des textes traduits que renvoient certains endpoints (par
exemple les noms des outils dans modèles et outils). Les messages d'erreur sont
toujours en anglais.
L'en-tête ne s'applique qu'à cette requête. Il ne modifie jamais la langue que l'utilisateur a choisie dans l'application AYETO, et qu'AYETO utilise pour les e-mails de l'utilisateur et les tâches planifiées.
Paramètres
Les paramètres sont envoyés dans le corps JSON, sauf indication contraire sur la page de l'endpoint. Les endpoints qui
agissent sur un seul enregistrement par identifiant (.../get, .../delete) prennent l'identifiant dans le corps sous la forme
{"id": "<uuid>"} :
curl -X POST "https://ayeto.ai/api/v3/conversation/get" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90"}'
Ils acceptent aussi l'identifiant dans le paramètre de requête entity_id, comme l'exigeaient les versions antérieures de
l'API ; le corps peut alors être omis ou valoir {}. Un corps que vous envoyez doit être du JSON
avec Content-Type: application/json, comme pour tout endpoint :
curl -X POST "https://ayeto.ai/api/v3/conversation/get?entity_id=0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90" \
-H "uni-api-key: $AYETO_API_KEY"
Une requête sans aucun des deux, avec les deux définis sur des identifiants différents, ou avec un id qui n'est pas un
UUID est rejetée avec 422.
Un endpoint dont les paramètres sont un objet JSON exige un corps, même si vous voulez toutes
les valeurs par défaut : envoyez {}. Une requête sans corps est rejetée avec 422.
Valeurs
- Les identifiants sont des UUID, écrits sous forme de chaînes :
"0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90". Les réponses utilisent toujours la forme en minuscules avec tirets. - Les horodatages sont des entiers : des millisecondes depuis l'epoch Unix (UTC), par exemple
1790846045000pour 2026-10-01 09:14:05 UTC.0signifie « jamais ».
Réponses
Une requête réussie renvoie 200 avec un corps JSON : un objet, un tableau d'objets
(endpoints find), un nombre (endpoints count) ou l'identifiant de l'enregistrement concerné sous forme de
chaîne JSON (endpoints delete). Les endpoints en streaming renvoient un stream
à la place. Les erreurs sont décrites dans Erreurs.
Champs communs
Chaque enregistrement stocké que renvoie l'API (une conversation, un assistant, un workflow, une exécution de workflow, ...) porte son identifiant et trois objets de métadonnées :
| Champ | Type | Description |
|---|---|---|
id |
UUID | Identifiant de l'enregistrement. |
created |
objet | Quand et par qui l'enregistrement a été créé. |
updated |
objet | Quand et par qui il a été modifié pour la dernière fois. timestamp vaut 0 s'il n'a jamais été modifié. |
accessed |
objet | Quand et par qui il a été écrit ou ouvert pour la dernière fois. La lecture de certains enregistrements (par exemple une conversation via conversation/get) le met à jour, au plus environ une fois par minute. |
Chaque objet de métadonnées a la même forme :
| Champ | Type | Description |
|---|---|---|
timestamp |
horodatage | Moment de l'événement, 0 s'il n'a pas eu lieu. |
user_id |
UUID ou null |
Utilisateur qui l'a provoqué ; null lorsqu'il a été effectué par le système. |
{
"id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
"created": { "timestamp": 1790846045000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
"updated": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
"accessed": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" }
}
La plupart des endpoints renvoient une vue publique de l'enregistrement : ces champs plus les champs listés sur la page de l'endpoint. Les endpoints qui renvoient l'enregistrement complet (par exemple les workflows et les exécutions de workflow) incluent aussi ces champs généraux :
| Champ | Type | Description |
|---|---|---|
owner |
UUID ou null |
Référence à usage général, généralement null. |
parent |
UUID ou null |
Référence à usage général, généralement null. |
source |
UUID ou null |
Enregistrement auquel celui-ci appartient, s'il y en a un. |
seq |
entier | Numéro de séquence de l'enregistrement au sein de son type. |
enabled |
booléen | Généralement true. |
note |
chaîne | Note en texte libre, généralement vide. |
owner_group |
UUID ou null |
Groupe propriétaire de l'enregistrement (par exemple les administrateurs d'une organisation) ; null lorsqu'il appartient à l'utilisateur qui l'a créé. |
permissions |
objet | Indicateurs d'accès group, all et other, chacun { "read": boolean, "write": boolean }. |
joined_collections |
null |
Toujours null dans cette API. |
created, updated, accessed, permissions et owner_group sont gérés par le
serveur. Les valeurs que vous envoyez pour ces champs dans les requêtes de création ou de mise à jour sont ignorées.
Interroger des listes
Les endpoints qui listent des enregistrements (.../find) et qui les comptent (.../count) prennent le même
objet de requête comme corps. L'exemple utilisé tout au long de cette section est
POST /api/v3/conversation/find.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
filters |
tableau | non | Conditions que les enregistrements doivent remplir, voir Filtres. Toutes les conditions du tableau doivent être remplies. |
sort_key |
chaîne | non | Champ de tri, par exemple "name" ou "updated.timestamp" (notation pointée pour les champs imbriqués). Il doit s'agir d'un champ de l'enregistrement ; un champ inconnu est rejeté avec 422. |
sort_order |
entier | non | 0 = croissant (par défaut lorsque sort_key est défini), 1 = décroissant. Ignoré sans sort_key. |
limit_from |
entier | non | Index du premier enregistrement à renvoyer, à partir de 0. La pagination n'est appliquée que lorsque ce champ est défini. |
limit_to |
entier | non | Index après le dernier enregistrement à renvoyer. Il s'agit d'une position absolue, pas d'une taille de page : limit_from: 20, limit_to: 40 renvoie les enregistrements 20 à 39. null ou 0 signifie « jusqu'à la fin ». |
fetch_dict |
booléen | non | N'a aucun effet sur les données renvoyées ; vous pouvez l'omettre. |
join |
tableau | non | Non pris en charge par l'API d'intégration ; ignoré. |
Sans sort_key, les enregistrements sont renvoyés du plus récent au plus ancien (par created.timestamp,
décroissant). Sans limit_from, tous les enregistrements correspondants sont renvoyés dans une seule réponse.
Pagination
Pour lire la page n (en comptant à partir de 0) de taille s, envoyez limit_from: n * s et
limit_to: (n + 1) * s. Règles :
limit_fromdoit valoir0ou plus etlimit_todoit être supérieur àlimit_from. Unlimit_fromnégatif ou unlimit_toinférieur àlimit_fromest rejeté avec422. Lorsque les deux sont égaux, aucune borne supérieure n'est appliquée et tous les enregistrements à partir delimit_fromsont renvoyés.limit_tosanslimit_fromest ignoré : le résultat complet est renvoyé.- Il n'existe pas de taille de page maximale côté serveur. Gardez des pages raisonnablement petites (jusqu'à quelques centaines d'enregistrements) ; les enregistrements volumineux, comme les conversations avec un long historique, produisent de grosses réponses.
curl -X POST "https://ayeto.ai/api/v3/conversation/find" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"filters": [["updated.timestamp", ">=", 1790812800000]],
"sort_key": "updated.timestamp",
"sort_order": 1,
"limit_from": 0,
"limit_to": 20
}'
La réponse est un tableau d'enregistrements ; un tableau vide lorsque rien ne correspond :
[
{
"id": "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
"name": "Quarterly report outline",
"model": "gpt-5-mini",
"summary": "",
"organization_id": null,
"assistant_id": "c4e2a9b1-7f3d-4e6a-8b5c-1d9f0a2e3b47",
"task_id": null,
"task_execution_id": null,
"workflow_id": null,
"workflow_run_id": null,
"messages": [
{
"id": "7d3a1f9e-2b4c-4e8a-9f6d-0c5b8e1a2d36",
"timestamp": 1790846045000,
"role": "user",
"content": "Draft an outline for the Q3 report.",
"reasoning_content": null,
"model": null,
"attachments": [],
"tool_runs": {}
},
{
"id": "e1b9c7a3-5d2f-4a6e-8c0b-9f4d3e2a1b58",
"timestamp": 1790846052000,
"role": "assistant",
"content": "1. Summary\n2. Revenue\n3. Costs\n4. Outlook",
"reasoning_content": null,
"model": "gpt-5-mini",
"attachments": [],
"tool_runs": {}
}
],
"public_sharing": false,
"created": { "timestamp": 1790846045000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
"updated": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" },
"accessed": { "timestamp": 1790846052000, "user_id": "5a1d7e3c-9b2f-4c8e-a6d0-3f7b1e9c2a54" }
}
]
Pour tout lire, demandez des pages jusqu'à ce que l'une d'elles soit plus courte que la taille de page.
Triez par created.timestamp croissant, afin que les enregistrements créés pendant la pagination soient
ajoutés à la fin au lieu de décaler les pages que vous n'avez pas encore lues :
import os
import requests
BASE = "https://ayeto.ai/api/v3"
HEADERS = {"uni-api-key": os.environ["AYETO_API_KEY"]}
PAGE = 50
start = 0
while True:
r = requests.post(f"{BASE}/conversation/find", headers=HEADERS, json={
"sort_key": "created.timestamp",
"sort_order": 0,
"limit_from": start,
"limit_to": start + PAGE,
})
r.raise_for_status()
page = r.json()
for conversation in page:
print(conversation["id"], conversation["name"])
if len(page) < PAGE:
break
start += PAGE
Comptage
Les endpoints .../count prennent le même corps et renvoient le nombre d'enregistrements qui correspondent à
filters sous forme d'entier simple. Ils ignorent limit_from, limit_to et les champs de
tri, si bien que vous pouvez envoyer le même corps que pour la page affichée et obtenir le total
pour la pagination.
curl -X POST "https://ayeto.ai/api/v3/workflow/count" \
-H "uni-api-key: $AYETO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"filters": [["name", "regex", "invoice"]], "limit_from": 0, "limit_to": 20}'
42
Toutes les listes n'ont pas d'endpoint de comptage ; les pages des endpoints indiquent ceux qui existent.
Filtres
filters est un tableau. Chaque élément est une condition ou un groupe de conditions, et un
enregistrement n'est renvoyé que s'il correspond à tous les éléments.
Conditions
Une condition est un tableau [field, operator, value], avec un quatrième élément facultatif
(voir Conversion des UUID) :
["model", "==", "gpt-5-mini"]
field désigne un champ de l'enregistrement. Utilisez la notation pointée pour les champs imbriqués
(created.timestamp, created.user_id). Un chemin vers une liste d'objets correspond lorsque
n'importe quel élément de la liste correspond (messages.role). id est l'identifiant de l'enregistrement.
| Opérateur | Correspond lorsque le champ... | Valeur |
|---|---|---|
== |
est égal à la valeur | toute valeur autorisée |
!= |
n'est pas égal à la valeur | toute valeur autorisée |
< |
est inférieur à la valeur | nombre ou chaîne |
> |
est supérieur à la valeur | nombre ou chaîne |
<= |
est inférieur ou égal à la valeur | nombre ou chaîne |
>= |
est supérieur ou égal à la valeur | nombre ou chaîne |
regex |
contient une correspondance de l'expression régulière, sans tenir compte de la casse | chaîne |
in |
est égal à l'une des valeurs | tableau non vide, au plus 100 valeurs |
Remarques :
regexest toujours insensible à la casse et non ancrée :"invoice"correspond à"Invoices 2026". Utilisez^et$dans le motif pour l'ancrer, et échappez les caractères d'expression régulière (.,*,+,?,(,),[,],\) pour les faire correspondre littéralement.- Les chaînes sont comparées alphabétiquement (par code de caractère), les nombres numériquement. Comparez les horodatages en tant que nombres.
["field", "==", null]correspond aux enregistrements dont le champ vautnullou est absent.
Groupes
Un groupe est un objet avec exactement une clé, AND ou OR, dont la valeur est un tableau non vide
de conditions ou d'autres groupes. Les groupes peuvent être imbriqués.
{"OR": [["model", "==", "gpt-5-mini"], ["model", "==", "gpt-5"]]}
{"AND": [
["updated.timestamp", ">=", 1788220800000],
{"OR": [["name", "regex", "report"], ["summary", "regex", "report"]]}
]}
La clé doit être écrite en majuscules. Un objet avec toute autre clé (par exemple
"or") est ignoré et correspond à tous les enregistrements, si bien qu'une faute de frappe désactive silencieusement cette partie
du filtre. Un objet sans clé ou avec plus d'une clé, ainsi qu'un groupe dont la valeur n'est
pas un tableau non vide, sont rejetés avec 422. Les groupes peuvent être imbriqués jusqu'à 32 niveaux.
Exemples
{
"filters": [
["assistant_id", "==", "c4e2a9b1-7f3d-4e6a-8b5c-1d9f0a2e3b47"],
["created.timestamp", ">=", 1788220800000],
["name", "regex", "^draft"]
]
}
{
"filters": [
["id", "in", [
"0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
"9e7a5c3b-1d2f-4b6a-8e0c-4f3d2a1b9c87"
]]
]
}
{
"filters": [
{"OR": [["organization_id", "==", null], ["organization_id", "==", "2f9c4b7e-8a1d-4e3c-b6f0-5d7a9e2c1b34"]]}
]
}
Le serveur vérifie et normalise chaque condition avant d'exécuter la requête. La plupart des résultats vides surprenants proviennent de l'une des règles ci-dessous.
Conversion des UUID
Une valeur de type chaîne qui est un UUID valide est convertie en UUID avant la comparaison. Les champs qui
contiennent des identifiants (id, assistant_id, created.user_id, ...) sont stockés sous forme d'UUID, c'est donc
ce que vous voulez pour eux. Un champ qui contient du texte ressemblant par hasard à un UUID
(y compris 32 chiffres hexadécimaux sans tirets) ne correspondra plus après la conversion.
Ajoutez true comme quatrième élément pour comparer la valeur en tant que texte brut :
["external_ref", "==", "0b8f3c2e5d414a7e9c1f6e2d8a4b7c90", true]
Avec in, le quatrième élément s'applique à chaque valeur de la liste. Les valeurs à l'intérieur d'un
tableau utilisé avec == ou != ne sont jamais converties.
Échappement HTML
Dans les valeurs de type chaîne, <, >, " et ' sont remplacés par <, >, " et
' avant la comparaison. Un filtre portant sur un texte qui contient ces caractères ne
correspond donc pas aux enregistrements stockés avec les caractères bruts ; filtrez sur une partie du texte
qui ne les contient pas, par exemple avec regex.
Limites des valeurs
| Valeur | Règle |
|---|---|
| chaîne | Au plus 1 000 caractères. Ne doit pas commencer par $. Les caractères nuls sont supprimés. |
| tableau | Au plus 100 éléments (y compris pour in). Chaque élément doit être une valeur autorisée. in exige au moins un élément. |
| types autorisés | chaîne, nombre, booléen, null, tableau. Un objet comme valeur est rejeté. |
Noms de champs
Un nom de champ doit commencer par une lettre et ne contenir que des lettres, des chiffres, _ et .
(au plus 128 caractères). Les autres noms sont rejetés avec 422.
Les noms de champs dans les filtres ne sont pas vérifiés par rapport à l'enregistrement : un champ mal orthographié ne correspond à
rien avec == et à tout avec !=. (sort_key, en revanche, est vérifié.)
Conditions mal formées
Une condition de moins de trois ou de plus de quatre éléments, ou un élément de
filters (ou d'un groupe) qui n'est ni un tableau ni un objet, est rejeté avec
422. detail indique le problème, par exemple
A filter condition must be [field, operator, value] with an optional fourth element, got 2 elements.
Erreurs
Les erreurs utilisent les codes de statut HTTP standard et un corps JSON avec un champ detail :
{"detail": "entity not found, id: 0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90"}
detail est un message lisible en anglais. Ne l'analysez pas, sauf lorsqu'une page d'endpoint
documente un message précis.
Lorsque la requête elle-même ne correspond pas au schéma de l'endpoint (un champ manquant ou de
mauvais type, un UUID invalide, un en-tête manquant, pas de corps), le statut est 422 et
detail est une liste qui pointe vers chaque problème :
{
"detail": [
{
"loc": ["body", "sort_order"],
"msg": "value is not a valid enumeration member; permitted: 0, 1",
"type": "type_error.enum",
"ctx": {"enum_values": [0, 1]}
}
]
}
loc est l'emplacement du problème : body, query ou header, suivi du
chemin du champ.
Une réponse diffère de cette forme : une défaillance inattendue du serveur peut renvoyer 500
avec le corps en texte brut Internal Server Error.
Lisez d'abord le code de statut et considérez le corps comme une information facultative.
| Statut | Signification | Que faire |
|---|---|---|
401 |
La clé API est vide, inconnue, supprimée, désactivée ou n'a pas une portée exigée par l'endpoint. detail vaut API key not provided ou API key is invalid. |
Vérifiez la clé et ses portées. Ne réessayez pas sans modification. |
403 |
La clé est valide, mais son utilisateur n'a pas le droit de faire cela : l'enregistrement appartient à quelqu'un d'autre, ou le compte n'a pas l'autorisation. detail vaut généralement permission denied. |
Ne réessayez pas. |
404 |
L'enregistrement n'existe pas (ou a été supprimé). | Ne réessayez pas. |
422 |
La requête est invalide : erreurs de schéma (detail sous forme de liste, voir ci-dessus), un filtre ou une clé de tri rejetés, ou une règle métier. L'épuisement des crédits renvoie aussi 422, avec detail not enough user credit ou not enough organization credit. |
Corrigez la requête, ou rechargez des crédits. |
423 |
La ressource est verrouillée par une autre opération (par exemple un enregistrement de la base de données booster). | Réessayez après une courte pause. |
429 |
Limite de débit dépassée, voir Limites de débit. | Attendez Retry-After secondes. |
500 |
Erreur du serveur. | Réessayez plus tard avec un backoff ; signalez-la si elle persiste. |
502, 503, 504, 529 |
Un fournisseur d'IA ou un autre service en amont a échoué ou est surchargé. | Réessayez plus tard avec un backoff. |
L'API n'utilise pas 402 ; les erreurs de crédits sont des 422, comme décrit ci-dessus. Un en-tête
uni-api-key absent (par opposition à une valeur vide ou erronée) est une erreur de schéma et
renvoie 422, pas 401.
Les réponses aux vérifications de clé API échouées (401) et aux requêtes soumises à la limite de débit (429) sont
volontairement retardées d'environ une seconde, pour ralentir les tentatives de deviner une clé. Tenez-en compte
dans vos délais d'expiration et ne considérez pas ce délai comme un problème du serveur.
Les erreurs qui surviennent après le début d'un stream sont signalées à l'intérieur du stream, et non par un statut HTTP.
Limites de débit
Les requêtes sont soumises à une limite de débit par adresse IP du client et par endpoint : chaque chemin d'endpoint a ses propres compteurs, et toutes les requêtes d'une même adresse IP vers cet endpoint sont comptées ensemble, quelle que soit la clé API utilisée. Les requêtes sont comptées même lorsqu'elles échouent, y compris les requêtes rejetées pour une mauvaise clé API.
Chaque endpoint a trois limites vérifiées en même temps : par minute, par heure et par jour. Ce sont des fenêtres glissantes : une requête compte dans une fenêtre pendant toute la durée de cette fenêtre après avoir été effectuée, si bien que la capacité se libère progressivement plutôt qu'au début de chaque minute ou heure. Chaque page d'endpoint indique son niveau :
| Niveau | Par minute | Par heure | Par jour |
|---|---|---|---|
| low | 10 | 100 | 1 000 |
| default | 60 | 2 000 | 20 000 |
| high | 120 | 4 000 | 40 000 |
| static | 1 200 | 30 000 | 300 000 |
| webhook | 6 000 | 120 000 | 1 500 000 |
Le niveau webhook s'applique uniquement au webhook de workflow public, qui limite en plus chaque déclencheur séparément. Le niveau static est une protection contre l'afflux de requêtes pour les endpoints interrogés souvent, comme la version du serveur.
Lorsqu'une limite est dépassée, l'API renvoie 429 avec un en-tête Retry-After : le nombre
de secondes entières avant que la fenêtre n'ait de la place pour une nouvelle requête.
HTTP/1.1 429 Too Many Requests
Retry-After: 17
Content-Type: application/json
{"detail": "Too many requests"}
Les réponses réussies ne portent aucun en-tête de limite de débit ; suivez donc vous-même votre débit de requêtes. Pour rester dans les limites :
- Attendez au moins
Retry-Aftersecondes avant de réessayer ; ne réessayez jamais un429dans une boucle serrée. - En cas d'échecs répétés (
429,5xx), utilisez un backoff exponentiel avec une part d'aléa (jitter). - Étalez les traitements par lots dans le temps au lieu de les envoyer en rafales, et mettez en cache les listes que vous lisez souvent au lieu de les récupérer à chaque utilisation.
- Plusieurs workers derrière une même adresse IP partagent les limites ; prévoyez un budget pour l'ensemble.
Streaming
Le chat et certains endpoints de workflow peuvent transmettre leur sortie en streaming : la réponse est envoyée en chunks pendant sa génération, au lieu d'un seul corps JSON à la fin. Le format des chunks, le signal de fin de stream et la façon dont les erreurs sont signalées en cours de stream sont décrits dans Streaming.