Ir al contenido

SDK y ejemplos

Todo lo que necesitas para llamar a Shara desde tu código: autenticación por token Bearer, el endpoint que una API key puede usar y ejemplos listos para copiar en curl, TypeScript (fetch) y Python. La API es REST plana sobre HTTPS, con cuerpos y respuestas en JSON, así que cualquier cliente HTTP estándar sirve. Todavía no hay SDK público: los SDK oficiales llegan con la disponibilidad general (GA) y el paquete interno es privado.

El host es https://api.sharasaas.com. El transporte es HTTPS obligatorio (TLS 1.2+), el cuerpo de las peticiones y las respuestas son JSON (Content-Type: application/json) y el modo streaming devuelve Server-Sent Events (text/event-stream). La inferencia y la base de datos están alojadas en la Unión Europea.

Propiedad Valor
Endpoint POST https://api.sharasaas.com/v1/inference
Protocolo HTTPS (TLS 1.2+)
Formato application/json · streaming en text/event-stream
Autenticación Authorization: Bearer <api-key>
Rate limit 60 req/min por IP y ruta · 600 req/min por workspace y ruta (cabeceras X-RateLimit-*)
Tamaño máximo del cuerpo 512 KB
Trazabilidad Cabecera X-Request-Id en toda respuesta

Las integraciones se autentican con una API key de Shara. La generas desde Ajustes → Seguridad y la envías en cada petición con la cabecera estándar Authorization:

Authorization: Bearer shara_sk_live_TU_API_KEY

La clave tiene el prefijo fijo shara_sk_live_ seguido de una parte secreta. Se muestra completa una sola vez, en el momento de crearla: guárdala en tu gestor de secretos. Después, el panel solo enseña el prefijo para que la identifiques. Cada clave lleva scopes (permisos mínimos; para invocar agentes necesita amadeus:invoke, o amadeus:invoke:agent:<slug> para un solo agente) y caduca por defecto a los 90 días (365 como máximo).

Rotar una clave revoca primero la antigua y después crea la nueva, así que hay un corte. Para evitarlo: crea la clave nueva, despliégala y solo entonces revoca la antigua. Las rutas de gestión de claves, los scopes y la caducidad están en la página API REST.

Trata la API key como una contraseña de servidor: nunca la incrustes en el navegador, en apps móviles ni en repositorios públicos. Vive en variables de entorno del backend. Si sospechas que se ha filtrado, revócala desde Ajustes → Seguridad y emite una nueva.

Una API key funciona en POST /v1/inference (y en el bridge MCP, con el scope mcp:tenant). El orquestador del workspace es Amadeus: recibe tu petición, decide qué agente departamental la atiende y coordina el trabajo. Puedes dirigirte a Amadeus (y dejar que enrute) o a un agente concreto por su slug, en el campo agentSlug.

Método Ruta Qué hace
POST /v1/inference Envía el turno a un agente y devuelve el resultado en la misma respuesta (normal o SSE).

Rutas como /runs y /usage/current solo funcionan con una sesión iniciada en la app, no con API key. Shara todavía no ofrece webhooks entrantes. La lista completa de rutas está en la página API REST.

Cada agente tiene un slug de API, y la API usa siempre el slug, sea cual sea el pack de nombres que haya elegido tu empresa. La tabla completa (nombre público, departamento y slug) está en el catálogo de agentes. El slug es el nombre público del agente en minúsculas (amadeus, wanamaker, patterson, graham, holmes, kerckhoffs…).

Los agentes disponibles para tu empresa dependen de tu plan y de los agentes que hayas elegido o contratado. Un agente que no tienes disponible responde 403.

Una petición mínima envía un alias de modelo y la lista de messages. El alias es lo único que se acepta para elegir el modelo: Prelude (ágil y económico, en todos los planes), Sonata (equilibrio coste/calidad, desde Pro) y Symphony (el de más capacidad, desde Max) son los modelos en la nube, en orden creciente de capacidad; Concerto es el modelo local del plan Concerto Local. Tu plan fija el techo: pedir un alias por encima devuelve 422 alias_plan_mismatch.

curl -sS https://api.sharasaas.com/v1/inference \
  -H "Authorization: Bearer shara_sk_live_TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alias": "Sonata",
    "agentSlug": "amadeus",
    "messages": [
      { "role": "user", "content": "Resume las novedades de soporte de esta semana." }
    ],
    "effort": "medium"
  }'

La llamada es síncrona: la respuesta (200) trae el alias usado, el content (ya redactado), el desglose usage en tokens y cómo terminó el turno en status (completed, failed, blocked o canceled; léelo, porque un turno fallido o bloqueado sigue siendo HTTP 200):

{
  "alias": "Sonata",
  "content": "Esta semana soporte cerró 34 tickets…",
  "usage": {
    "inputTokens": 184,
    "outputTokens": 412
  },
  "status": "completed",
  "runId": "3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64"
}

Según el turno, puede traer también toolOutcome, blockedReason y motivoDeCorte. Todos los campos están descritos en API REST.

Con "stream": true la respuesta llega como text/event-stream: fragmentos como data: {"delta":"…"} y un evento de cierre end con status, runId y el recuento de tokens. Usa curl -N para verlo en directo:

curl -sS -N https://api.sharasaas.com/v1/inference \
  -H "Authorization: Bearer shara_sk_live_TU_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "alias": "Prelude",
    "stream": true,
    "messages": [{ "role": "user", "content": "Escribe un saludo de dos líneas." }]
  }'
data: {"delta":"Hola, "}

data: {"delta":"bienvenido."}

event: end
data: {"done":true,"status":"completed","runId":"3f8a1c52-9d4e-4b7a-a1c3-5e2f7d9b0a64","alias":"Prelude","usage":{"inputTokens":21,"outputTokens":9}}

El flujo puede traer otros eventos con nombre (por ejemplo usage o error); ignora los que no uses.

Equivalente en Node.js / TypeScript con fetch nativo. Decide siempre por el campo error (una cadena estable), nunca por el texto de message:

const BASE = 'https://api.sharasaas.com';
const API_KEY = process.env.SHARA_API_KEY; // nunca en el cliente
if (API_KEY === undefined) throw new Error('Falta SHARA_API_KEY');

interface InferenceResponse {
  alias: string;
  content: string;
  usage: { inputTokens: number; outputTokens: number };
  status: 'completed' | 'failed' | 'blocked' | 'canceled';
  runId?: string;
  toolOutcome?: { rejected: string[]; expired: string[] };
  blockedReason?: string;
  motivoDeCorte?: 'tope_de_pasos' | 'tope_de_gasto' | 'sin_cierre';
}

const res = await fetch(`${BASE}/v1/inference`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    alias: 'Sonata',
    agentSlug: 'amadeus',
    messages: [
      { role: 'user', content: 'Resume las novedades de soporte de esta semana.' },
    ],
    effort: 'medium',
  }),
});

if (!res.ok) {
  const { error } = (await res.json()) as { error: string };
  throw new Error(`Shara API ${res.status}: ${error}`);
}

const data = (await res.json()) as InferenceResponse;
console.log(data.status, data.content, data.usage);

Y leyendo el flujo:

const stream = await fetch(`${BASE}/v1/inference`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    alias: 'Prelude',
    stream: true,
    messages: [{ role: 'user', content: 'Escribe un saludo de dos líneas.' }],
  }),
});
if (!stream.ok || stream.body === null) throw new Error(`Shara API ${stream.status}`);

const reader = stream.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const blocks = buffer.split('\n\n');
  buffer = blocks.pop() ?? '';
  for (const block of blocks) {
    const event = /^event: (.+)$/m.exec(block)?.[1] ?? 'message';
    const raw = /^data: (.+)$/m.exec(block)?.[1];
    if (raw === undefined) continue;
    let payload: { delta?: string; status?: string; runId?: string };
    try {
      payload = JSON.parse(raw);
    } catch {
      continue; // no es un evento JSON: se ignora
    }
    if (event === 'message' && payload.delta !== undefined) process.stdout.write(payload.delta);
    if (event === 'end') console.log('\n', payload.status, payload.runId);
  }
}
import json
import os

import requests

BASE = "https://api.sharasaas.com"
HEADERS = {"Authorization": f"Bearer {os.environ['SHARA_API_KEY']}"}  # solo en el servidor

# Modo normal: el resultado viene en esta misma respuesta.
resp = requests.post(
    f"{BASE}/v1/inference",
    headers=HEADERS,
    json={
        "alias": "Sonata",
        "agentSlug": "amadeus",
        "messages": [{"role": "user", "content": "Resume las novedades de soporte de esta semana."}],
        "effort": "medium",
    },
)
if not resp.ok:
    raise RuntimeError(f"Shara API {resp.status_code}: {resp.json().get('error')}")
data = resp.json()
print(data["status"], data["content"])

# Modo streaming (SSE).
with requests.post(
    f"{BASE}/v1/inference",
    headers=HEADERS,
    json={
        "alias": "Prelude",
        "stream": True,
        "messages": [{"role": "user", "content": "Escribe un saludo de dos líneas."}],
    },
    stream=True,
) as stream:
    stream.raise_for_status()
    stream.encoding = "utf-8"
    event = "message"
    for line in stream.iter_lines(decode_unicode=True):
        if not line:
            event = "message"
            continue
        if line.startswith("event: "):
            event = line[len("event: "):]
        elif line.startswith("data: "):
            try:
                payload = json.loads(line[len("data: "):])
            except json.JSONDecodeError:
                continue  # no es un evento JSON: se ignora
            if event == "message" and "delta" in payload:
                print(payload["delta"], end="", flush=True)
            elif event == "end":
                print("\n", payload.get("status"), payload.get("runId"))

Las respuestas nunca revelan el modelo real que hay detrás de un alias. Un redactor procesa toda salida (contenido, mensajes de error y volcados de herramientas) antes de que cruce la red, así que solo verás Prelude · Sonata · Symphony · Concerto. No dependas de nombres de proveedor: no aparecerán.

El límite usa una ventana fija de 60 segundos con un contador por ruta: 60 peticiones por IP y ruta y 600 peticiones por workspace y ruta. Cada respuesta lleva cabeceras para que tu cliente se regule solo; úsalas en lugar de adivinar:

  • X-RateLimit-Limit: tope de peticiones de la ventana actual.
  • X-RateLimit-Remaining: peticiones que te quedan; frena antes de llegar a 0.
  • X-RateLimit-Reset: epoch (segundos) en que se reinicia la ventana.
  • Retry-After: solo en 429, segundos a esperar antes de reintentar.

Cuando superas el límite recibes 429 con { "error": "rate_limit_exceeded", "retryAfterSec": <n> }; respeta Retry-After con backoff exponencial. Ten presentes además dos cortes distintos del límite de peticiones: quota_exceeded cuando tu cuota de STU llega al nivel de bloqueo (el 200 % de la cuota), y kill_switch cuando salta un tope de gasto (por defecto 1 M de STU por ejecución, 10 M por hora y 80 M por día; un owner o administrador puede ajustarlos o desactivarlos). Ambos son también 429, pero se resuelven esperando al reinicio de la cuota o revisando el consumo, no reintentando en bucle.

Con la disponibilidad general (GA) publicaremos SDKs oficiales para Node.js / TypeScript y Python que envolverán autenticación, reintentos y streaming. Mientras tanto, los ejemplos de esta página cubren el endpoint que una API key puede usar: es REST plana, con cabeceras estándar y JSON, y funciona con cualquier cliente HTTP.

¿Quieres acceso anticipado a la API o subir tu límite de peticiones por workspace? Escríbenos a api@aiginer.com con tu caso de uso, o a soporte@aiginer.com para cualquier duda de integración.