Guides

Conventions

Format des requêtes, champs communs, requêtes de listes et filtres, erreurs et limites de débit communs à tous les endpoints.

Afficher en Markdown

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 :

text
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>"} :

bash
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 :

bash
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 1790846045000 pour 2026-10-01 09:14:05 UTC. 0 signifie « 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.
json
{
  "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_from doit valoir 0 ou plus et limit_to doit être supérieur à limit_from. Un limit_from négatif ou un limit_to inférieur à limit_from est rejeté avec 422. Lorsque les deux sont égaux, aucune borne supérieure n'est appliquée et tous les enregistrements à partir de limit_from sont renvoyés.
  • limit_to sans limit_from est 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.
bash
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 :

json
[
  {
    "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 :

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

bash
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}'
json
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) :

json
["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 :

  • regex est 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 vaut null ou 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.

json
{"OR": [["model", "==", "gpt-5-mini"], ["model", "==", "gpt-5"]]}
json
{"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

json
{
  "filters": [
    ["assistant_id", "==", "c4e2a9b1-7f3d-4e6a-8b5c-1d9f0a2e3b47"],
    ["created.timestamp", ">=", 1788220800000],
    ["name", "regex", "^draft"]
  ]
}
json
{
  "filters": [
    ["id", "in", [
      "0b8f3c2e-5d41-4a7e-9c1f-6e2d8a4b7c90",
      "9e7a5c3b-1d2f-4b6a-8e0c-4f3d2a1b9c87"
    ]]
  ]
}
json
{
  "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 :

json
["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 &lt;, &gt;, &quot; et &#x27; 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 :

json
{"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 :

json
{
  "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
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-After secondes avant de réessayer ; ne réessayez jamais un 429 dans 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.