Documentation
Soumettez des tâches d'inférence vérifiée en un seul appel Python. Recevez en retour des embeddings numpy et un reçu inviolable qui prouve l'exactitude du résultat. Propulsé par le paquet cyberian-client.
Aperçu
Vous nommez un modèle, vous envoyez vos entrées, vous récupérez vos sorties accompagnées d'un reçu inviolable. La plateforme s'occupe de l'exécution, de la vérification indépendante, et des attestations cryptographiques qui rendent le résultat auditable.
- API synchrone : un seul appel soumet et attend.
- Exceptions typées : distingue auth, quota, limite de débit, échecs de vérification.
- Sorties numpy : les embeddings reviennent sous forme de
np.ndarray(shape=(N, D), dtype=float32).
Installation
Python 3.9+ requis.
pip install cyberian-client
Voir sur PyPI : pypi.org/project/cyberian-client et consultez l'historique des versions, les empreintes des paquets, le fil de publications.
Authentification
Obtenez une clé via la page d'inscription. Passez-la au constructeur du client ou définissez la variable d'environnement CYBERIAN_API_KEY.
from cyberian import Client
client = Client(api_key="cyb_votretiquette_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
# ou : client = Client() # lit CYBERIAN_API_KEY depuis l'environnement
Soumettre votre première tâche
L'appel le plus simple est submit_and_wait - il soumet, interroge jusqu'au règlement, puis récupère le reçu et le tableau de sortie.
from cyberian import Client
client = Client()
texts = [
"Le vif renard brun saute par-dessus le chien paresseux.",
"Cyberian Systems vérifie l'inférence IA de bout en bout.",
"Les reçus Merkle rendent les sorties auditables.",
]
result = client.submit_and_wait(
model="bge-large-v1.5",
inputs=texts,
)
print("Tâche :", result.job["id"])
print("Reçu :", result.receipt["receipt_id"])
print("Forme :", result.embeddings.shape) # (3, 1024)
print("Type :", result.embeddings.dtype) # float32
Étape par étape (miroir de l'enchaînement REST)
Les mêmes cinq appels que parcourt le Flux typique REST, exprimés en méthodes SDK. Utile lorsque vous voulez un contrôle explicite sur chaque étape (p. ex. inspecter la tâche avant règlement, isoler la passe de vérification pour un journal d'audit).
# 1. soumettre une tâche
job = client.submit_job(model="bge-large-v1.5", inputs=texts)
print(job["id"], job["status"]) # EXECUTING
# 2. bloquer jusqu'à un état terminal (SETTLED / FAILED / REFUNDED)
settled = client.wait_for_completion(job["id"], timeout_sec=600)
# 3. récupérer le reçu Merkle allégé
receipt = client.get_receipt(settled["receipt_id"])
# 4. passe de vérification côté serveur — recalcule chaque hachage
check = client.verify_receipt(settled["receipt_id"])
assert check["valid"], check["errors"]
# 5. récupérer les embeddings (remise en forme via les en-têtes pour vous)
embeddings = client.get_job_output(settled["id"]) # np.ndarray(N, D)
Modèles
Chaque tâche nomme un model (model="..."). Pour lister les modèles activés pour votre compte :
info = client.list_models()
print(info["models"])
La plateforme est agnostique au modèle. Vous avez besoin d'un modèle qui n'est pas activé pour vous ? Écrivez à support@cyberiansystems.ai avec la référence du modèle (par ex. une URL Hugging Face). L'intégration d'un nouveau modèle est une étape de configuration de notre côté - aucun changement de code chez vous.
Vérifier un reçu
Chaque tâche réglée produit un reçu inviolable qui ancre les entrées, les sorties et l'attestation de vérification dans des racines Merkle cryptographiques. Vous pouvez le valider hors ligne :
receipt = client.get_receipt(result.receipt["receipt_id"])
verified = client.verify_receipt(receipt["receipt_id"])
assert verified["valid"] is True
print(verified["checks"])
Si un seul octet du reçu est modifié, le vérificateur retourne valid=False avec des détails sur la cause - c'est la piste d'audit sur laquelle vos clients (ou les régulateurs) peuvent s'appuyer.
Champs du reçu
| Champ | Description |
|---|---|
receipt_id | Identifiant unique du reçu. |
job_id | Tâche que ce reçu atteste. |
spec_hash | Hachage opaque de la spécification d'exécution utilisée. |
inputs_root | Racine Merkle de vos entrées. |
outputs_root | Racine Merkle des sorties produites. |
verification_attestation | Racine Merkle engageant le travail de vérification indépendante. |
executor_attestation_ids | Identifiants opaques des fournisseurs de calcul impliqués. |
issued_at | Horodatage ISO-8601 de l'émission. |
receipt_hash | SHA-256 auto-référentiel - ce que verify_receipt recalcule. |
Vérification en tant que service
Si vous exécutez l'inférence sur votre propre infrastructure et avez besoin d'une attestation indépendante, utilisez le point de terminaison VaaS. Vous soumettez vos entrées et le SHA-256 de vos sorties calculées ; la plateforme vérifie indépendamment et émet un reçu.
import hashlib
mes_outputs_hash = hashlib.sha256(mes_embeddings.tobytes()).hexdigest()
receipt = client.verify_outputs(
model="bge-large-v1.5",
inputs=texts,
claimed_output_commitment=mes_outputs_hash,
)
Compte
client.get_account() retourne un dictionnaire décrivant votre compte, l'utilisation courante et les clés associées :
| Champ | Description |
|---|---|
account_id | UUID de votre compte. |
name | Nom affiché. |
email | Courriel du compte. |
username | Nom d'utilisateur du compte. |
email_verified_at | Horodatage ISO-8601, ou null si non vérifié. |
tier | Palier assigné (par ex. "free"). |
effective_tier | Palier réellement facturé (change lorsque l'essai expire). |
subscription_status | "trial" | "active" | "none". |
trial_started_at | Horodatage ISO-8601. |
trial_ends_at | Horodatage ISO-8601. |
chunks_consumed_period | Chunks consommés dans la période courante. |
chunks_period_started_at | Horodatage ISO-8601 du début de période. |
limits | Objet avec les plafonds du palier effectif - voir ci-dessous. |
keys | Tableau de descripteurs de clé - voir ci-dessous. |
limits
| Champ | Description |
|---|---|
chunks_per_period | Plafond de chunks par période. |
period_length_days | Longueur de la période, en jours. |
chunks_per_day | Plafond quotidien. |
chunks_per_minute | Plafond soutenu par minute. |
max_chunks_per_request | Plafond de chunks par requête. |
max_inputs | Nombre maximum d'entrées par requête. |
keys[i]
| Champ | Description |
|---|---|
key_id | Préfixe de 12 caractères - à passer à revoke_key. |
label | Étiquette donnée à la clé lors de sa création. |
created_at | Horodatage ISO-8601. |
last_used_at | Horodatage ISO-8601, ou null si jamais utilisée. |
revoked_at | Horodatage ISO-8601, ou null si active. |
Tous les horodatages sont des chaînes ISO-8601 en UTC.
Erreurs et reprises
Le client lève des exceptions typées que vous pouvez attraper de façon granulaire :
| Exception | Cause | Action recommandée |
|---|---|---|
AuthError | Clé API invalide ou révoquée | Réémettre la clé sur la page de compte |
TrialExpiredError | Période d'essai écoulée | Nous écrire pour mise à niveau ou prolongation |
QuotaError | Quota périodique ou quotidien atteint | Attendre la réinitialisation, ou en demander plus |
RateLimitError | Débit par minute dépassé | Reculer ; le SDK expose retry_after |
ServiceBusyError | Profondeur de file ou capacité atteinte | Réessayer avec backoff |
JobFailedError | Un ou plusieurs chunks ont échoué à la vérification | Inspecter err.failed_chunks |
VerificationFailedError | Le reçu n'a pas validé hors ligne | Traiter la sortie comme non fiable |
NetworkError | Erreur de transport (aucune réponse HTTP reçue) | wait_for_completion réessaie automatiquement ; ailleurs, réessayer avec backoff |
ApiError | Autres 4xx/5xx | Inspecter err.status et err.body |
from cyberian import Client, RateLimitError, QuotaError
client = Client()
try:
result = client.submit_and_wait(model="bge-large-v1.5", inputs=texts)
except RateLimitError as e:
time.sleep(e.retry_after)
except QuotaError:
print("Chunks d'essai épuisés - écrivez-nous pour prolonger.")
Limites de l'essai
- 14 jours à partir de la vérification du courriel (pas de l'inscription — le compteur démarre lorsque vous cliquez sur le lien de confirmation)
- 400 chunks par période d'essai
- 100 chunks par jour
- 60 chunks par minute en régime soutenu
- Maximum 100 chunks par requête, jusqu'à 1 000 textes d'entrée par requête
Les limites au-delà de l'essai sont fournies sur demande - écrivez à support@cyberiansystems.ai.
Référence du client
Constructeur
Client(api_key: str | None = None)
Si api_key est omis, le client lit CYBERIAN_API_KEY depuis l'environnement.
Méthodes
| Méthode | Retour | Description |
|---|---|---|
submit_job(model, inputs) | dict | Soumettre une tâche. |
get_job(job_id) | dict | Récupérer l'état courant. |
wait_for_completion(job_id, timeout_sec=600) | dict | Attend que la tâche atteigne SETTLED, FAILED, ou délai écoulé. |
get_job_output(job_id) | np.ndarray | Tenseur de sortie, dtype float32. |
get_receipt(receipt_id) | dict | Le reçu public - voir « Champs du reçu » ci-dessus. |
verify_receipt(receipt_id) | dict | Valider le reçu. |
submit_and_wait(model, inputs) | JobResult | Commodité tout-en-un. |
verify_outputs(model, inputs, claimed_output_commitment) | dict | Voie VaaS - reçu pour vos sorties. |
list_models() | dict | Modèles activés pour votre compte. |
get_account() | dict | Profil, compteurs d'utilisation, liste de clés. |
JobResult
@dataclass
class JobResult:
job: dict
receipt: dict
embeddings: np.ndarray
verification: dict | None
Support
Questions, rapports de bogues, demandes de mise à niveau : support@cyberiansystems.ai.
HTTP direct pour tout ce qui n'est pas Python. Mêmes points de terminaison que ceux appelés par le SDK Python en coulisses, même clé API, mêmes quotas d'essai. Toutes les routes orientées client se trouvent sur https://api.cyberiansystems.ai.
Aperçu
L'authentification utilise un jeton Bearer. Le format est JSON pour tout sauf GET /jobs/:id/output, qui retourne des octets float32 bruts (les embeddings eux-mêmes). Tous les horodatages sont en ISO-8601 UTC.
cyb_… reçue à l'inscription (ou régénérée depuis) fonctionne tant pour le SDK Python que pour l'API REST directe. Aucune autre clé n'est requise.Le SDK Python est une couche fine au-dessus de ces points de terminaison — pour voir ce qui circule, activez logging.getLogger("httpx").setLevel("DEBUG") dans un script Python et soumettez une tâche. Tout ce qui suit est ce que vous verrez.
Authentification
Chaque point de terminaison marqué BEARER exige :
Authorization: Bearer cyb_…votre-clé…
Le seul point de terminaison sans authentification est GET /health (sonde de connectivité, non documentée davantage). Tout le reste — y compris GET /models — exige un jeton Bearer.
Les clés n'expirent jamais d'elles-mêmes. Faites-les tourner régulièrement via POST /auth/keys/rotate ; révoquez une clé compromise via POST /auth/keys/revoke.
Flux typique
Cinq appels séquentiels couvrent l'enchaînement soumission → attente → récupération → vérification → téléchargement. Copiez-collez ; les variables shell circulent. Requiert $CYBERIAN_API_KEY dans l'environnement et jq dans le PATH.
# 1. soumettre une tâche → capturer l'id de tâche
export JOB_ID=$(curl -sS -X POST https://api.cyberiansystems.ai/jobs \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"bge-large-v1.5","inputs":["bonjour","monde"]}' \
| jq -r .id)
echo "job=$JOB_ID"
# 2. long-poll jusqu'à un état terminal (SETTLED / FAILED / REFUNDED)
# chaque attente est plafonnée à 30s côté serveur ; bouclez côté client si plus long.
export RECEIPT_ID=$(curl -sS "https://api.cyberiansystems.ai/jobs/$JOB_ID/wait?max_wait=30" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
| jq -r .receipt_id)
echo "receipt=$RECEIPT_ID"
# 3. récupérer le reçu Merkle allégé (votre artefact d'audit)
curl -sS "https://api.cyberiansystems.ai/receipts/$RECEIPT_ID" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" | jq
# 4. passe de vérification côté serveur — recalcule chaque hachage du reçu
curl -sS "https://api.cyberiansystems.ai/receipts/$RECEIPT_ID/verify" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" | jq
# 5. récupérer les embeddings float32 bruts + en-tête de forme pour remise en forme
curl -sS "https://api.cyberiansystems.ai/jobs/$JOB_ID/output" \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
--output embeddings.bin -D headers.txt
grep X-Cyberian headers.txt # X-Cyberian-Shape + X-Cyberian-Dtype
Si $RECEIPT_ID vaut null après l'étape 2, la tâche n'a pas atteint SETTLED dans la fenêtre d'attente. Relancez l'étape 2 avec le même $JOB_ID ; l'appel se débloque dès que la tâche se termine. Pour la gestion binaire de l'étape 5 côté Python, la méthode get_job_output() du SDK reformate pour vous via les en-têtes — voir l'onglet Python.
GET/modelsBEARER
Liste les modèles activés pour votre compte. La réponse est aujourd'hui le catalogue complet pour chaque appelant authentifié ; le filtrage par compte arrive en Phase 3.
curl https://api.cyberiansystems.ai/models \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Réponse (200) :
{
"models": [
{
"id": "bge-large-v1.5",
"name": "BGE Large (English) v1.5",
"description": "Embeddings de phrases 1024 dim. Déterministe sur CPU.",
"output_dimensions": 1024,
"max_input_length_tokens": 512
}
]
}
POST/jobsBEARER
Soumettez un lot d'entrées pour inférence vérifiée. La réponse synchrone renvoie la tâche en EXECUTING ; le travail se fait côté plateforme et vous interrogez (ou utilisez le long-poll) pour le reçu.
Corps de requête :
| Champ | Type | Requis | Description |
|---|---|---|---|
model | chaîne | oui | L'un des ID de modèle retournés par GET /models. |
inputs | tableau<chaîne> | oui | Textes à embedder. 1 à 1000 par requête ; chacun jusqu'à 64 Ko. |
curl -X POST https://api.cyberiansystems.ai/jobs \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"bge-large-v1.5","inputs":["bonjour","monde"]}'
Réponse (201) :
{
"id": "7c4f…uuid",
"status": "EXECUTING",
"chunk_count": 1,
"receipt_id": null,
"created_at": "2026-05-14T20:15:00.000Z"
}
Erreurs : 400 (requête invalide / modèle inconnu), 401 (Bearer manquant ou invalide), 402 (essai expiré / quota épuisé), 413 (requête trop grande), 429 (limité), 503 (plateforme saturée).
GET/jobs/:idBEARER
État courant d'une tâche. Renvoie 404 (et non 403) pour les tâches existantes mais appartenant à un autre compte — l'existence d'un UUID n'est jamais divulguée entre comptes.
curl https://api.cyberiansystems.ai/jobs/$JOB_ID \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Réponse (200) :
{
"id": "7c4f…uuid",
"status": "SETTLED", // ou EXECUTING / PROVING / FAILED / REFUNDED
"chunk_count": 1,
"receipt_id": "r-9f…uuid",
"created_at": "2026-05-14T20:15:00.000Z"
}
GET/jobs/:id/wait?max_wait=NBEARER
Long-poll : la requête se bloque côté serveur jusqu'à ce que la tâche atteigne un état terminal (SETTLED, FAILED, REFUNDED) ou que max_wait secondes s'écoulent, selon ce qui survient en premier. Plafonné à 30 secondes par requête ; bouclez côté client pour des attentes plus longues.
curl "https://api.cyberiansystems.ai/jobs/$JOB_ID/wait?max_wait=30" \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Le corps de réponse a la même forme que GET /jobs/:id. Moins coûteux côté plateforme que le polling actif.
GET/jobs/:id/resultsBEARER
Progression par chunk. Utile pendant que la tâche est encore EXECUTING pour visibilité partielle, ou après règlement pour un journal d'audit.
{
"job_id": "7c4f…uuid",
"status": "SETTLED",
"receipt_id": "r-9f…uuid",
"chunks": [
{
"chunk_index": 0,
"row_start": 0,
"row_end": 2,
"status": "VERIFIED",
"output_commitment": "sha256-hex…",
"selected_for_verification": true
}
]
}
GET/jobs/:id/outputBEARER
Embeddings concaténés en octets float32 bruts, little-endian, dans l'ordre des entrées. Disponible seulement quand la tâche atteint SETTLED.
Remettre en forme via les en-têtes que nous renvoyons :
| En-tête | Signification |
|---|---|
Content-Type: application/octet-stream | Corps binaire. |
X-Cyberian-Shape: rows,cols | P. ex. 2,1024 pour 2 entrées via bge-large. |
X-Cyberian-Dtype: float32 | Fixe pour la charge d'embedding. |
curl https://api.cyberiansystems.ai/jobs/$JOB_ID/output \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
--output embeddings.bin -D headers.txt
grep X-Cyberian headers.txt
POST/verifyBEARER
Vérification en tant que service. Vous avez exécuté l'inférence vous-même ; nous la re-exécutons sur notre infrastructure et renvoyons soit un reçu confirmant votre sortie (200), soit un rejet (422). Synchrone — bloque jusqu'à la fin de la vérification ou expiration.
/verify accepte aujourd'hui une seule forme de revendication — un engagement SHA-256 de vos sorties concaténées (le champ claimed_output_commitment ci-dessous). D'autres types arrivent, chacun avec sa propre forme de paramètres ; chacun sera documenté à sa sortie.Corps de requête :
| Champ | Type | Requis | Description |
|---|---|---|---|
model | chaîne | oui | L'un des ID du catalogue. |
inputs | tableau<chaîne> | oui | Les entrées que vous avez données à votre modèle. Mêmes plafonds que POST /jobs. |
claimed_output_commitment | chaîne | oui | SHA-256 hex des octets de sortie concaténés produits par votre modèle. Nous comparons aux nôtres. |
Réponse (200, vérifié) : reçu allégé (même forme que GET /receipts/:id).
Réponse (422, désaccord) :
{
"error": "verification_failed",
"reason": "prover replay did not match claimed output_commitment",
"job_id": "7c4f…uuid"
}
GET/receipts/:idBEARER
Le reçu allégé visible par le client — l'artefact cryptographique que vous conservez pour l'audit. Se vérifie hors-ligne via verify_receipt_offline() du SDK Python, ou en ligne via le point de terminaison suivant.
Réponse (200) :
{
"receipt_id": "r-9f…uuid",
"job_id": "7c4f…uuid",
"spec_hash": "sha256-hex…",
"inputs_root": "sha256-hex…",
"outputs_root": "sha256-hex…",
"verification_attestation": "sha256-hex…",
"verification_level": "standard",
"executor_attestation_ids": ["cyb-exec-…"],
"issued_at": "2026-05-14T20:15:42.000Z",
"receipt_hash": "sha256-hex…"
}
GET/receipts/:id/verifyBEARER
Recalcul côté serveur de chaque hachage du reçu. Utilisez ceci quand vous voulez que la plateforme atteste son propre reçu sans faire confiance à votre code local.
{
"valid": true,
"checks": {
"receipt_hash": true,
"inputs_root": true,
"outputs_root": true,
"verification_attestation": true
},
"errors": []
}
GET/accountBEARER
Votre compte : palier, statut d'essai, usage de la période, liste des clés (métadonnées seulement — jamais les clés en clair).
{
"account_id": "acct-uuid…",
"email": "vous@entreprise.com",
"username": "vous",
"email_verified_at": "2026-05-01T12:00:00.000Z",
"name": "Entreprise",
"tier": "free",
"effective_tier": "trial",
"subscription_status": "trial",
"trial_started_at": "2026-05-01T12:00:00.000Z",
"trial_ends_at": "2026-05-15T12:00:00.000Z",
"chunks_consumed_period": 37,
"chunks_period_started_at": "2026-05-01T12:00:00.000Z",
"limits": {
"chunks_per_period": 400,
"period_length_days": 14,
"chunks_per_day": 100,
"chunks_per_minute": 60,
"max_chunks_per_request": 100,
"max_input_texts": 1000
},
"keys": [
{
"key_id": "cyb_…12car",
"label": "trial",
"created_at": "2026-05-01T12:00:00.000Z",
"last_used_at": "2026-05-14T20:14:00.000Z",
"revoked_at": null
}
]
}
POST/auth/keys/rotateBEARER
Émet une nouvelle clé. L'ancienne reste valide jusqu'à ce que vous la révoquiez explicitement, vous laissant le temps de mettre vos déploiements à jour. La nouvelle clé en clair est retournée une seule fois ; elle n'est plus jamais affichée.
curl -X POST https://api.cyberiansystems.ai/auth/keys/rotate \
-H "Authorization: Bearer $CYBERIAN_API_KEY"
Réponse (201) :
{
"api_key": "cyb_label_32carshex…",
"key_id": "cyb_…12car"
}
POST/auth/keys/revokeBEARER
Invalide une clé via son key_id (le préfixe de 12 caractères, pas le secret complet). Idempotent — révoquer une clé déjà révoquée retourne 200.
curl -X POST https://api.cyberiansystems.ai/auth/keys/revoke \
-H "Authorization: Bearer $CYBERIAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"key_id":"cyb_…12car"}'
rotate (ce qui en émet une nouvelle), puis révoquez l'ancienne avec son key_id.Erreurs
Corps JSON sur chaque réponse non-2xx, forme { "error": "<code machine>", "message"?: "…" }. Le code HTTP porte le plus d'information ; le champ error sert aux switch.
| Statut | error | Signification |
|---|---|---|
| 400 | invalid_request | Corps malformé ou champ inconnu. |
| 400 | unknown_model | ID de modèle absent du catalogue. Écrivez à models@cyberiansystems.ai pour en demander l'ajout. |
| 401 | unauthorized | Bearer manquant, malformé ou révoqué. |
| 402 | trial_expired / quota_exceeded | Compteur d'essai écoulé ou quota de période épuisé. |
| 404 | not_found | La ressource n'existe pas ou appartient à un autre compte. |
| 413 | request_too_large | Dépasse max_chunks_per_request ou max_input_texts. |
| 422 | verification_failed | Le rejeu VaaS ne concorde pas avec votre engagement de sortie. |
| 429 | rate_limited / too_many_inflight | Bucket par minute ou plafond en-vol. Respectez l'en-tête Retry-After. |
| 503 | service_busy | File de la plateforme saturée. Respectez Retry-After. |
| 504 | verification_timeout | Le rejeu VaaS n'a pas terminé dans la fenêtre de la requête. |
Limites de débit
Mêmes plafonds d'essai que côté SDK Python — voir Limites de l'essai sur l'onglet Python. La plateforme les applique côté serveur ; pas besoin de throttling côté client pour un usage normal.
Sur 429 / 503, nous renvoyons Retry-After en secondes. Respectez-le. La boucle correcte est « back-off et réessayer » ; le SDK le fait automatiquement.
Le règlement on-chain et l'ancrage de reçus on-chain sont en préparation. L'interface n'est pas encore publiée.
À venir
Les clients souhaitant ancrer leurs reçus d'inférence vérifiée sur une blockchain publique (Base, via séquestre en USDC + preuves Noir / EZKL de validité de reçu) choisiront cette option de règlement à la soumission. D'ici là :
- Le règlement hors-chaîne (Stripe) est la seule voie de facturation. Les utilisateurs d'essai ne paient rien ; le palier payant gérera la facturation.
- Les reçus sont déjà cryptographiques, simplement non-ancrés. Vous pouvez déjà publier le
receipt_hashd'un reçu sur n'importe quel service d'horodatage de votre choix (p. ex. OpenTimestamps) pour une preuve d'émission attestable par un tiers.
Si le règlement on-chain est un prérequis pour vous, écrivez à support@cyberiansystems.ai avec votre cas d'usage — nous prioriserons la fonctionnalité selon la traction client.