# Autenticación (/es/authentication)
Envía tu clave de API como token bearer en cada solicitud:
```bash
curl https://api.upsky.dev/v1/platform/team \
-H "Authorization: Bearer $UPSKY_API_KEY"
```
## Tipos de clave [#tipos-de-clave]
| Prefijo | Entorno | Úsala para |
| ------------- | ------- | ---------------------------------------------------------- |
| `upsky_test_` | Prueba | Desarrollo. No genera cargos ni ejecuta verificaciones reales. |
| `upsky_live_` | En vivo | Tráfico de producción. |
Las claves pertenecen a un equipo, así que siguen funcionando cuando un integrante se va.
## Mantén tus claves seguras [#mantén-tus-claves-seguras]
- Usa las claves solo en tu servidor. Nunca las incluyas en un navegador o una app móvil.
- Guárdalas en variables de entorno, no en tu código.
- Reemplaza una clave de inmediato si crees que se filtró.
## Rota una clave [#rota-una-clave]
Crea una clave nueva, despliégala y después elimina la anterior. Puedes tener más de una clave activa, así que no hay tiempo de inactividad.
Los agentes de IA usan OAuth en lugar de claves de API. Consulta [autenticación de MCP](/es/mcp/authentication).
# Errores (/es/errors)
Todas las API devuelven los errores con la misma estructura:
```json
{
"error": {
"code": "invalid_request",
"message": "The field 'email' is required.",
"request_id": "req_123"
}
}
```
Incluye `request_id` cuando contactes a soporte.
## Códigos de estado [#códigos-de-estado]
| Estado | Código | Qué hacer |
| ------ | ------------------------ | ------------------------------------------------------------------ |
| `400` | `invalid_request` | Corrige la solicitud. Reintentar no ayudará. |
| `401` | `unauthorized` | Revisa que tu clave de API sea correcta y no esté revocada. |
| `402` | `insufficient_credits` | Agrega créditos o activa la recarga automática y reintenta. |
| `403` | `pro_required` | Suscríbete a Pro. Las solicitudes en vivo a productos de pago lo requieren. |
| `403` | `account_suspended` | Contacta a soporte. |
| `403` | `forbidden` | Tu clave no tiene acceso a este recurso. |
| `404` | `not_found` | Revisa el ID y si estás en modo de prueba o en vivo. |
| `409` | `conflict` | El recurso no permite eso ahora, como cancelar una verificación ya enviada. |
| `409` | `idempotency_key_reused` | Usa un `Idempotency-Key` nuevo para una solicitud distinta. |
| `409` | `request_in_progress` | El primer intento sigue en curso. Reintenta en un momento. |
| `409` | `retry_later` | Espera los segundos de `Retry-After` y reintenta. |
| `413` | `payload_too_large` | Envía un cuerpo de menos de 1 MB. |
| `429` | `rate_limited` | Espera y reintenta. Consulta los [límites](/es/rate-limits). |
| `500` | `internal_error` | Reintenta con espera progresiva. |
| `503` | `service_unavailable` | Reintenta con espera progresiva. |
Decide según `code`, no según `message`: los códigos son estables y los mensajes pueden cambiar de redacción.
Todas las respuestas, exitosas o no, también llevan el ID en el encabezado `X-Request-Id`.
## Reintentar de forma segura [#reintentar-de-forma-segura]
Agrega el encabezado `Idempotency-Key` a las solicitudes `POST`. Sirve cualquier texto único de hasta 255 caracteres, como un UUID.
- Si la primera solicitud tuvo éxito, un reintento con la misma clave devuelve esa misma respuesta, con el encabezado `Idempotent-Replayed: true`, y el trabajo no se repite.
- Si la primera solicitud fue rechazada o falló, no se guarda nada, así que un reintento la ejecuta de nuevo.
- Las claves se recuerdan durante 24 horas, por separado en modo de prueba y en vivo.
# Introducción (/es)
Upsky le da a tu app verificación de identidad, modelos de IA, almacenamiento de archivos y hosting con una sola cuenta y una sola clave de API.
## Productos [#productos]
Confirma quién está detrás de cada cuenta.
Usa todos los modelos de IA con una sola clave.
Guarda y entrega los archivos de tu app.
Despliega y ejecuta sitios, desde Next.js hasta WordPress.
## Conéctate a la plataforma [#conéctate-a-la-plataforma]
Administra equipos, dominios, claves de API y uso desde tu propio código.
Permite que agentes de IA trabajen con tu cuenta de Upsky.
## ¿Eres nuevo? [#eres-nuevo]
Empieza con la [guía de inicio rápido](/es/quickstart) y luego revisa cómo funciona la [autenticación](/es/authentication).
## Para herramientas de IA [#para-herramientas-de-ia]
Cada página está disponible en Markdown si agregas `.md` a su URL. El sitio completo está en [`/es/llms.txt`](/es/llms.txt) y [`/es/llms-full.txt`](/es/llms-full.txt).
# Inicio rápido (/es/quickstart)
## 1. Crea una clave de API [#1-crea-una-clave-de-api]
En tu panel de Upsky, abre la configuración de tu equipo y crea una clave de API. Copia una clave live de inmediato, porque solo se muestra una vez. Una clave de prueba se puede volver a mostrar desde la lista.
## 2. Guárdala como variable de entorno [#2-guárdala-como-variable-de-entorno]
```bash
export UPSKY_API_KEY="upsky_test_..."
```
## 3. Haz una solicitud [#3-haz-una-solicitud]
Lista los modelos disponibles para tu equipo:
```bash
curl https://api.upsky.dev/v1/ai/models \
-H "Authorization: Bearer $UPSKY_API_KEY"
```
## Siguientes pasos [#siguientes-pasos]
- [Autenticación](/es/authentication): claves de prueba, claves en vivo y cómo rotarlas
- [Errores](/es/errors): qué hacer cuando falla una solicitud
- [Webhooks](/es/webhooks): entérate de los eventos en cuanto ocurren
# Límites y paginación (/es/rate-limits)
## Límites de solicitudes [#límites-de-solicitudes]
Los límites aplican a todo tu equipo, así que todas las claves los comparten:
| Claves | Solicitudes por minuto |
| --------- | ---------------------- |
| En vivo | 600 |
| De prueba | 120 |
Cada respuesta incluye tu límite actual:
| Encabezado | Significado |
| --------------------- | ------------------------------------------ |
| `RateLimit-Limit` | Solicitudes permitidas en la ventana actual |
| `RateLimit-Remaining` | Solicitudes restantes en la ventana actual |
| `RateLimit-Reset` | Segundos para que se reinicie la ventana |
Si superas el límite recibes un `429` con el encabezado `Retry-After`. Espera esa cantidad de segundos antes de volver a intentarlo.
## Paginación [#paginación]
Los endpoints de lista devuelven una página de resultados y un cursor:
```json
{
"data": [],
"next_cursor": "cur_123"
}
```
Envía `next_cursor` como `?cursor=` para obtener la siguiente página. Cuando sea `null`, llegaste al final. Usa `?limit=` para definir el tamaño de la página, hasta 100.
# Webhooks (/es/webhooks)
Agrega un endpoint en tu panel, en **Equipo → Webhooks**, y Upsky le enviará los
eventos con `POST` en cuanto ocurran. Cada endpoint es de modo de prueba o real
y solo recibe los eventos de ese modo.
```json
{
"id": "6b2c9f3e-1a4d-4e5f-9b8a-7c6d5e4f3a2b",
"object": "event",
"type": "verify.session.approved",
"livemode": false,
"created_at": "2026-11-12T10:01:38Z",
"data": { "object": { "object": "verify.session", "id": "…", "status": "approved" } }
}
```
## Comprueba la firma [#comprueba-la-firma]
Las entregas siguen el formato [Standard Webhooks](https://www.standardwebhooks.com)
y van firmadas con el secreto de tu endpoint (`whsec_…`). Cada solicitud trae los
encabezados `webhook-id`, `webhook-timestamp` y `webhook-signature`. Revísalos con
cualquier librería de Standard Webhooks antes de confiar en el contenido:
```ts
import { Webhook } from "standardwebhooks"
const event = new Webhook(process.env.UPSKY_WEBHOOK_SECRET).verify(
rawBody,
headers
)
```
## Responde rápido [#responde-rápido]
Devuelve un `2xx` en menos de 10 segundos y haz el trabajo lento en segundo
plano. Una entrega fallida se reintenta con espera creciente hasta por dos días,
así que el mismo evento puede llegar más de una vez. Usa `id` para ignorar
duplicados.
## Eventos [#eventos]
| Evento | Se envía cuando |
| ----------------------------- | -------------------------------------------------- |
| `verify.session.approved` | Una verificación pasó, o se aprobó en revisión |
| `verify.session.rejected` | Una verificación falló, o se rechazó en revisión |
| `verify.session.needs_review` | Una verificación necesita que una persona decida |
| `verify.session.expired` | Un enlace no se terminó a tiempo |
| `verify.session.canceled` | Una verificación se canceló |
| `verify.session.redacted` | Se borraron los datos de una verificación |
Los eventos traen la sesión sin los datos leídos de la identificación. Consulta
la sesión con la API para obtenerlos.
# Imágenes (/es/ai/images)
Los modelos de imagen usan la API de Images de OpenAI, así que
`images.generate` funciona en cualquier SDK de OpenAI con la misma URL base y
clave que el [texto](/es/ai).
```ts
import { writeFile } from "node:fs/promises"
import OpenAI from "openai"
const ai = new OpenAI({
apiKey: process.env.UPSKY_API_KEY,
baseURL: "https://api.upsky.dev/v1/ai",
})
const image = await ai.images.generate({
model: "bfl/flux-2-pro",
prompt: "Un faro sobre un acantilado al amanecer, en acuarela",
size: "1024x1024",
})
await writeFile("faro.png", Buffer.from(image.data[0].b64_json, "base64"))
```
Obtén los modelos de imagen en el
[endpoint de modelos](/es/ai/reference/listModels) con `?type=image`.
## Opciones [#opciones]
- `n`: cuántas imágenes crear, de 1 a 4. Se cobra cada una.
- `size`: ancho y alto en píxeles, como `1024x1024`. Cada modelo admite sus
propios tamaños.
- `aspect_ratio`: la proporción entre ancho y alto, como `16:9`, para los
modelos que usan una proporción en lugar de un tamaño.
- `seed`: hace que los resultados se puedan repetir, en los modelos que lo
admiten.
Las imágenes siempre se devuelven en base64 en `b64_json`, nunca como
enlaces, y Upsky no las guarda. Los demás campos, como `quality` o `style`, se
ignoran.
## Cuánto cuesta [#cuánto-cuesta]
La mayoría de los modelos de imagen cobran un precio fijo por imagen, que el
endpoint de modelos indica en `pricing.image`. Algunos cobran más por tamaños
grandes, que aparecen en `pricing.image_sizes`. Un tamaño entre dos de la
lista cuesta lo mismo que el siguiente más grande, y un tamaño mayor que el
más grande se rechaza.
Algunos modelos, como los de OpenAI, se cobran por tokens, igual que un modelo
de texto. Upsky aparta una estimación antes de la solicitud y cobra lo que
indica el proveedor.
Solo se cobran las imágenes que se devuelven. `usage.cost` en la respuesta
indica lo que costó la solicitud, en dólares estadounidenses.
## Claves de prueba [#claves-de-prueba]
Una clave de prueba recibe un PNG transparente de 1×1 por cada imagen que
pida. Nunca llama a un modelo y nunca se cobra.
# Descripción general (/es/ai)
Upsky AI te permite usar modelos de los principales laboratorios con la clave
de API de Upsky de tu equipo, la misma que usan todos los productos de Upsky.
Usa la API de OpenAI, así que funciona con cualquier cliente o SDK de OpenAI
con solo cambiar su URL base. Los modelos de texto usan Chat Completions, y
también hay modelos de [imagen](/es/ai/images) y de [voz](/es/ai/speech).
## Conecta [#conecta]
Apunta tu cliente a `https://api.upsky.dev/v1/ai` y usa tu
[clave de API](/es/authentication):
```ts
import OpenAI from "openai"
const ai = new OpenAI({
apiKey: process.env.UPSKY_API_KEY,
baseURL: "https://api.upsky.dev/v1/ai",
})
const reply = await ai.chat.completions.create({
model: "anthropic/claude-sonnet-5",
messages: [{ role: "user", content: "Hola" }],
})
console.log(reply.choices[0].message.content)
```
O con cURL:
```bash
curl https://api.upsky.dev/v1/ai/chat/completions \
-H "Authorization: Bearer $UPSKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [{ "role": "user", "content": "Hola" }]
}'
```
## Elige un modelo [#elige-un-modelo]
Obtén la lista actual en el [endpoint de modelos](/es/ai/reference/listModels).
Los ID de los modelos siempre incluyen al proveedor, con el formato
`provider/model-name`. Cada modelo indica su `type`, que es `text`, `image` o
`speech`, y su precio: por millón de tokens para texto, por imagen para
imágenes y por millón de caracteres para voz. Agrega `?type=text` para listar
un solo tipo.
## Streaming [#streaming]
Usa `"stream": true` para recibir la respuesta como eventos enviados por el
servidor mientras se escribe. El último evento antes de `data: [DONE]` indica
los tokens usados y lo que costó la solicitud.
## Claves de prueba [#claves-de-prueba]
Una clave de prueba recibe una respuesta de ejemplo con el mismo formato que
una real, con streaming o sin él, para que puedas construir y probar tu
integración de principio a fin. Nunca llama a un modelo y nunca se cobra. El
modelo que indiques igual tiene que existir.
## Cuánto cuesta [#cuánto-cuesta]
Cada solicitud se paga con los créditos de tu equipo, al precio de la lista de
modelos. Las claves en vivo necesitan un plan Pro activo.
- Antes de que empiece una solicitud, Upsky aparta lo máximo que podría costar:
la instrucción más la respuesta más larga permitida. Solo se cobra lo que la
solicitud usó de verdad.
- Usa `max_completion_tokens` para limitar una respuesta. Sin este campo, un
equipo sin recarga automática recibe un límite menor que sus créditos
cubran, en lugar de un rechazo.
- Si tus créditos no cubren una solicitud y la recarga automática no puede
añadir más, recibes `402` con `insufficient_credits`.
- Si cierras un stream antes de tiempo, se cobra lo que el modelo escribió
hasta ese momento.
- `usage.cost` en cada respuesta indica lo que se cobró, en dólares
estadounidenses.
## Límites [#límites]
- Una opción por solicitud: `n` debe ser 1.
- Solo herramientas de tipo función. Las herramientas integradas, como la
búsqueda web de un proveedor, se rechazan.
- Solo el nivel de servicio predeterminado, sin nivel prioritario ni modo
rápido.
- Cuerpos de solicitud de hasta 4 MB, con imágenes y archivos incluidos.
- `Idempotency-Key` se ignora: cada solicitud vuelve a preguntar al modelo.
- `user` se descarta. Para distinguir a tus propios usuarios, envía
`safety_identifier`.
Los errores usan el mismo formato que el resto de la API. Consulta
[errores](/es/errors).
## Uso [#uso]
La página **AI** del panel muestra lo que gastó tu equipo este mes, día por
día, los modelos que más usa y sus solicitudes más recientes, de texto, imagen
y voz.
# Voz (/es/ai/speech)
Los modelos de voz usan la API de voz de OpenAI, así que
`audio.speech.create` funciona en cualquier SDK de OpenAI con la misma URL
base y clave que el [texto](/es/ai). La respuesta es el propio archivo de
audio.
```ts
import { writeFile } from "node:fs/promises"
import OpenAI from "openai"
const ai = new OpenAI({
apiKey: process.env.UPSKY_API_KEY,
baseURL: "https://api.upsky.dev/v1/ai",
})
const speech = await ai.audio.speech.create({
model: "openai/tts-1",
voice: "alloy",
input: "Tu pedido salió esta mañana.",
})
await writeFile("voz.mp3", Buffer.from(await speech.arrayBuffer()))
```
Obtén los modelos de voz en el
[endpoint de modelos](/es/ai/reference/listModels) con `?type=speech`.
## Opciones [#opciones]
- `input`: el texto que se leerá, hasta 5000 caracteres.
- `voice`: cada proveedor tiene sus propias voces, como `alloy` en OpenAI o
un ID de voz en ElevenLabs. Sin este campo, el modelo usa la suya
predeterminada.
- `response_format`: `mp3`, `opus`, `aac`, `flac`, `wav` o `pcm`. Un modelo
que no puede generar el formato que pides responde en el suyo, así que
revisa el encabezado `Content-Type`.
- `instructions`: cómo debe hablar, por ejemplo el tono o el ritmo, en los
modelos que lo admiten.
- `speed`: de 0.25 a 4, en los modelos que lo admiten.
- `language`: el idioma del texto, como `es`, cuando el modelo lo necesita.
Upsky no guarda el texto ni el audio.
## Cuánto cuesta [#cuánto-cuesta]
La voz se cobra por carácter de `input`, al precio que el endpoint de modelos
indica en `pricing.characters`, por millón de caracteres. El precio se conoce
antes de la solicitud, así que se cobra exactamente eso.
El encabezado `Upsky-Cost` de la respuesta indica lo que costó, en dólares
estadounidenses, y `Upsky-Characters` cuántos caracteres se cobraron.
Todavía no se ofrecen la música, los efectos de sonido ni las voces que se
cobran por segundo de audio.
## Claves de prueba [#claves-de-prueba]
Una clave de prueba recibe un segundo de silencio, en WAV, sin importar el
formato que pidas. Nunca llama a un modelo y nunca se cobra.
# Descripción general (/es/hosting)
Usa Upsky Hosting para crear sitios y desplegarlos desde tus propias herramientas.
## Despliega un sitio [#despliega-un-sitio]
```bash
curl https://api.upsky.dev/v1/hosting/sites/site_123/deployments \
-H "Authorization: Bearer $UPSKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "source": { "type": "git", "ref": "main" } }'
```
## Estado del despliegue [#estado-del-despliegue]
| Estado | Significado |
| ---------- | ----------------------------------- |
| `queued` | En espera de iniciar |
| `building` | Compilación en curso |
| `ready` | En línea |
| `failed` | Falló la compilación o la publicación |
Consulta el despliegue periódicamente o escucha los [webhooks](/es/webhooks) `deployment.ready` y `deployment.failed`.
# Autenticación (/es/mcp/authentication)
El servidor MCP usa OAuth, así que nunca pegas una clave de API en una herramienta de IA.
1. Tu herramienta de IA abre una página de inicio de sesión de Upsky.
2. Eliges un equipo y revisas lo que solicita la herramienta.
3. La herramienta recibe un token que solo funciona para ese equipo.
## Permisos [#permisos]
| Permiso | Permite |
| ------- | ------------------------------------------------ |
| `read` | Ver sitios, dominios, archivos y uso |
| `write` | Hacer cambios, como crear un despliegue |
Las herramientas que cuestan dinero o eliminan algo te piden confirmación primero.
## Quita el acceso [#quita-el-acceso]
Puedes revocar una herramienta conectada en cualquier momento desde la configuración de tu equipo. Deja de funcionar de inmediato.
# Conecta un cliente (/es/mcp/connect)
## Claude Code [#claude-code]
```bash
claude mcp add --transport http upsky https://mcp.upsky.dev/mcp
```
## Claude Desktop [#claude-desktop]
Abre **Settings**, ve a **Connectors**, agrega un conector personalizado y escribe:
```text
https://mcp.upsky.dev/mcp
```
## Cursor [#cursor]
Agrega esto a `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"upsky": {
"url": "https://mcp.upsky.dev/mcp"
}
}
}
```
## VS Code [#vs-code]
Agrega esto a `.vscode/mcp.json`:
```json
{
"servers": {
"upsky": {
"type": "http",
"url": "https://mcp.upsky.dev/mcp"
}
}
}
```
La primera vez que lo uses, se abrirá tu navegador para que [inicies sesión](/es/mcp/authentication).
# Descripción general (/es/mcp)
El servidor MCP de Upsky permite que herramientas de IA como Claude, Cursor y VS Code trabajen con tu cuenta mediante el [Model Context Protocol](https://modelcontextprotocol.io).
Una vez conectado, puedes pedir cosas como:
- "Despliega la rama main de mi sitio de marketing."
- "¿Está disponible `example.com`?"
- "¿Cuánto uso de IA tuvo mi equipo este mes?"
## URL del servidor [#url-del-servidor]
```text
https://mcp.upsky.dev/mcp
```
El servidor usa Streamable HTTP, así que no hay nada que instalar.
Configúralo en tu herramienta de IA.
Cómo obtienen los agentes acceso a tu equipo.
Todo lo que puede hacer un agente.
# Herramientas (/es/mcp/tools)
## Verify [#verify]
| Herramienta | Permiso | Qué hace |
| ----------------------- | ------- | ----------------------------------- |
| `verify_create_session` | `write` | Inicia una verificación de identidad |
| `verify_get_session` | `read` | Obtiene el estado de una verificación |
## IA [#ia]
| Herramienta | Permiso | Qué hace |
| ---------------- | ------- | ----------------------------- |
| `ai_list_models` | `read` | Lista los modelos disponibles |
## Storage [#storage]
| Herramienta | Permiso | Qué hace |
| -------------------- | ------- | ---------------------------------------- |
| `storage_list_files` | `read` | Lista los archivos de tu equipo |
| `storage_get_file` | `read` | Obtiene un enlace temporal a un archivo |
## Hosting [#hosting]
| Herramienta | Permiso | Qué hace |
| --------------------------- | ------- | -------------------------------- |
| `hosting_list_sites` | `read` | Lista tus sitios |
| `hosting_create_deployment` | `write` | Inicia un despliegue |
| `hosting_get_deployment` | `read` | Obtiene el estado de un despliegue |
## Plataforma [#plataforma]
| Herramienta | Permiso | Qué hace |
| ------------------------- | ------- | -------------------------------------- |
| `platform_get_team` | `read` | Obtiene los datos de tu equipo |
| `platform_search_domains` | `read` | Comprueba si un dominio está disponible |
| `platform_get_usage` | `read` | Obtiene el uso por producto |
# Descripción general (/es/platform)
Las API de producto son las que tu app llama mientras funciona. La API de la plataforma es para la cuenta en sí.
## Qué puedes administrar [#qué-puedes-administrar]
| Recurso | Qué puedes hacer |
| ------- | -------------------------------- |
| Equipo | Consultar los datos de tu equipo |
Aquí se agregarán más recursos conforme estén disponibles.
## Ejemplo [#ejemplo]
Una forma rápida de comprobar que una clave funciona y en qué modo está:
```bash
curl https://api.upsky.dev/v1/platform/team \
-H "Authorization: Bearer $UPSKY_API_KEY"
```
```json
{
"id": "0b9f6a52-6d0c-4a3e-9d1e-3f4b2a7c8d90",
"name": "Acme",
"livemode": false
}
```
## Claves de API [#claves-de-api]
Las claves se crean y se revocan en el panel de tu equipo, en **Claves de API**, por un propietario o administrador. No se pueden crear por la API, así que una clave filtrada no sirve para crear más claves.
¿Prefieres que lo haga un agente de IA? Usa el [servidor MCP](/es/mcp).
# Descripción general (/es/storage)
Upsky Storage le da a tus apps buckets privados que hablan la API de S3, así que
funcionan con cualquier cliente o SDK de S3. Las descargas siempre son gratis.
## Empieza [#empieza]
1. En el panel, abre **Storage** y crea un bucket. Cada equipo tiene un bucket
de 10 GB gratis.
2. Abre el bucket y crea una **llave de acceso**. Copia el Access Key ID y el
Secret Access Key: el secreto se muestra una sola vez.
3. Haz clic en **Conectar** en la página de Storage para copiar el endpoint. La
región siempre es `auto`.
Cada llave de acceso solo llega a un bucket. Crea una por app o entorno, para
poder eliminar una sin romper las demás.
## Conecta un cliente [#conecta-un-cliente]
```ts
import { S3Client } from "@aws-sdk/client-s3"
const storage = new S3Client({
region: "auto",
endpoint: process.env.UPSKY_STORAGE_ENDPOINT,
credentials: {
accessKeyId: process.env.UPSKY_STORAGE_ACCESS_KEY_ID,
secretAccessKey: process.env.UPSKY_STORAGE_SECRET_ACCESS_KEY,
},
})
```
Usa como `Bucket` en cada solicitud el nombre que aparece en la página del
bucket, que empieza con `upsky-`.
## Sube un archivo [#sube-un-archivo]
```ts
import { PutObjectCommand } from "@aws-sdk/client-s3"
await storage.send(
new PutObjectCommand({
Bucket: process.env.UPSKY_STORAGE_BUCKET,
Key: "reportes/2026/reporte.pdf",
Body: file,
ContentType: "application/pdf",
})
)
```
Una `/` en la llave funciona como carpeta, y el explorador de archivos del panel
la muestra así.
## Comparte un archivo [#comparte-un-archivo]
Los buckets son privados. Para que un navegador descargue o suba un archivo
directamente, firma una URL de corta duración en tu servidor:
```ts
import { GetObjectCommand } from "@aws-sdk/client-s3"
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
const url = await getSignedUrl(
storage,
new GetObjectCommand({
Bucket: process.env.UPSKY_STORAGE_BUCKET,
Key: "reportes/2026/reporte.pdf",
}),
{ expiresIn: 300 }
)
```
## Tamaños y uso [#tamaños-y-uso]
Cada tamaño incluye cierto número de escrituras y lecturas al mes. Las
escrituras son solicitudes que cambian o listan el bucket, como las subidas; las
lecturas son solicitudes que obtienen datos, como las descargas. Eliminar es
gratis.
Las escrituras y lecturas que pasen del incluido se pagan con tus créditos. Si
no se pueden pagar, las subidas nuevas se pausan hasta que agregues créditos, y
las lecturas siguen funcionando. Un bucket lleno también pausa las subidas hasta
que elimines archivos o cambies a un tamaño mayor.
Consulta los [precios](https://upsky.dev/es/pricing) para ver los tamaños y
precios actuales.
# Descripción general (/es/verify)
Usa Upsky Verify para comprobar la identidad de una persona antes de que use tu
app. Tiene [niveles de verificación](/es/verify/tiers), desde una selfie con
estimación de edad hasta una identificación oficial comparada con el rostro de
la persona.
## Cómo funciona [#cómo-funciona]
1. Tu servidor crea una sesión.
2. Envías a la persona a la `url` de la sesión, una página en
`verify.upsky.dev` que funciona en cualquier teléfono. También puedes
iniciarla desde el panel, que te da un código QR y un enlace para compartir.
3. La persona acepta el aviso, fotografía su identificación si el nivel lo
pide y se toma una selfie.
4. Upsky hace las revisiones y envía un [webhook](/es/webhooks) con el
resultado. También puedes consultar la sesión cuando quieras.
Solo pagas cuando una sesión llega a una decisión. Un enlace que expira o que
cancelas no cuesta nada.
## Crea una sesión [#crea-una-sesión]
```bash
curl https://api.upsky.dev/v1/verify/sessions \
-H "Authorization: Bearer $UPSKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "tier": "identity", "reference": "user_123", "return_url": "https://example.com/done" }'
```
| Campo | Valor por defecto | Qué hace |
| --------------------- | ----------------- | --------------------------------------------------------- |
| `tier` | `identity` | `age` o `identity` |
| `reference` | | Tu propio ID para la persona |
| `return_url` | | A dónde va la persona después de enviar |
| `minimum_age` | `18` | De 13 a 25 |
| `allow_selfie_upload` | `false` | Deja que la persona envíe una selfie desde sus fotos |
| `show_result` | `false` | Muestra el resultado a la persona en el enlace |
| `expires_in` | `86400` | Segundos que funciona el enlace, de una hora a siete días |
| `project_id` | | Uno de tus proyectos, para agrupar sesiones |
| `metadata` | | Hasta 20 claves propias |
| `options` | | Identificaciones aceptadas, reglas y retención de fotos |
| `expected` | | Lo que esperas que diga la identificación |
## Selfies [#selfies]
Por defecto, la selfie tiene que tomarse con la cámara en la página, en ese
momento. Pon `allow_selfie_upload` en `true` para que la persona también pueda
elegir una foto que ya tiene. La sesión devuelve el ajuste como
`allow_selfie_upload`, y el panel tiene el mismo interruptor al iniciar una
verificación.
Las sesiones reales siempre toman la selfie con la cámara, durante la prueba de
vida, sin importar este ajuste.
## Mostrar el resultado [#mostrar-el-resultado]
Por defecto, el enlace solo agradece a la persona y tú le dices el resultado.
Pon `show_result` en `true` para mostrarlo en el enlace: justo después de enviar
sus fotos, la página espera las revisiones y dice si quedó verificada, si no, o
si está en revisión. Nunca dice el motivo, para que la página no sirva para
averiguar qué pasa las revisiones. El panel tiene el mismo interruptor al
iniciar una verificación.
Si una sesión en revisión se aprueba o rechaza después, el enlace muestra el
nuevo resultado la próxima vez que se abra.
## Umbrales de aprobación [#umbrales-de-aprobación]
Qué tan segura debe estar cada revisión lo decide tu equipo, en el panel en
**Verify → Configuración**. Elige un preajuste, o define cada número con
Personalizado:
| Preajuste | Aprobación de vida | Revisión de vida | Aprobación de rostro | Revisión de rostro | Margen de edad |
| ----------- | ------------------ | ---------------- | -------------------- | ------------------ | -------------- |
| Flexible | 65 | 40 | 90 | 80 | 2 años |
| Equilibrado | 80 | 50 | 95 | 85 | 3 años |
| Estricto | 90 | 70 | 98 | 92 | 5 años |
Una puntuación igual o mayor que la marca de aprobación pasa. Desde la marca de
revisión hasta la de aprobación, la sesión va a revisión
(`liveness_inconclusive` o `face_match_inconclusive`), para que una persona la
vea. Por debajo de la marca de revisión se rechaza (`liveness_failed` o
`face_mismatch`). En el nivel `age`, una selfie sola pasa cuando la persona aparenta al menos
`minimum_age` más el margen de edad: el punto medio del rango estimado,
devuelto como `checks.age.estimated_age`. Para una verificación de 18+, pasa si
aparenta 20 en Flexible, 21 en Equilibrado y 23 en Estricto. Un rango completo
por debajo de `minimum_age` se rechaza, y lo que quede en medio va a revisión.
Equilibrado es el predeterminado. Un proyecto puede tener sus propios umbrales,
elegidos en el mismo lugar: una sesión con ese `project_id` los usa, y las demás
usan los de tu equipo. Cada sesión conserva los umbrales con los que
se creó, devueltos como `thresholds`, así que cambiar tu configuración nunca
cambia cómo se decide una sesión existente. Cuando una prueba de vida saca menos
que la marca de aprobación, la página sugiere intentarlo de nuevo, dentro de los
tres intentos del enlace.
## Identificaciones aceptadas [#identificaciones-aceptadas]
En el nivel `identity`, tú eliges qué identificaciones puede usar una persona y
de qué países, en el panel en **Verify → Configuración** o por sesión:
```bash
curl https://api.upsky.dev/v1/verify/sessions \
-H "Authorization: Bearer $UPSKY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"options": {
"documents": {
"allowed_types": ["passport", "mx_ine"],
"allowed_countries": ["MEX", "USA"]
}
}
}'
```
`allowed_types` es cualquiera de `mx_ine`, `passport`, `us_drivers_license` e
`id_card`. La persona solo ve las que aceptas, y no se le pide elegir cuando hay
una sola. `allowed_countries` recibe códigos ISO 3166-1 alfa-3; déjalo vacío
para aceptar cualquier país. La INE solo cuenta como `MEX` y la licencia de
conducir de EE. UU. como `USA`, así que se rechaza una solicitud que no acepte
ni un país que coincida ni otra identificación.
La identificación se lee en cuanto llegan sus fotos. Si está borrosa, no es del
tipo que la persona eligió, está vencida o es de un país que no aceptas, la
página lo dice y pide fotos nuevas, hasta `max_attempts` veces (3 de forma
predeterminada, de 1 a 5). Después del último intento la persona envía las
fotos que tiene, y tus reglas de resultado deciden.
## Reglas de resultado [#reglas-de-resultado]
Cada problema que una revisión puede encontrar lleva a uno de tres resultados:
rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo
(`ignore`). Configúralas en **Verify → Configuración**, o por sesión en
`options.rules`:
| Regla | Predeterminado | Puede ser |
| ------------------------------- | -------------- | ---------------------------- |
| `liveness_inconclusive` | `review` | `reject`, `review` |
| `age_inconclusive` | `review` | `reject`, `review` |
| `document_unreadable` | `reject` | `reject`, `review` |
| `document_expired` | `reject` | `reject`, `review`, `ignore` |
| `document_checksum_failed` | `reject` | `reject`, `review` |
| `document_type_mismatch` | `review` | `reject`, `review` |
| `document_country_not_accepted` | `reject` | `reject`, `review` |
| `date_of_birth_missing` | `review` | `reject`, `review` |
| `face_match_inconclusive` | `review` | `reject`, `review` |
| `age_estimate_mismatch` | `review` | `reject`, `review`, `ignore` |
| `name_mismatch` | `review` | `reject`, `review`, `ignore` |
| `date_of_birth_mismatch` | `review` | `reject`, `review`, `ignore` |
Una prueba de vida fallida, una selfie sin rostro, un rostro que no es el de la
identificación y una edad menor a la mínima siempre se rechazan.
## Datos esperados [#datos-esperados]
Si ya sabes quién debería ser la persona, envíalo como `expected` y la
identificación se compara con eso:
```json
{
"tier": "identity",
"expected": {
"first_name": "Ana",
"last_name": "García",
"date_of_birth": "1992-04-15"
}
}
```
Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres: "Ana"
coincide con "ANA MARIA". El resultado está en `checks.data_match`, campo por
campo: `match`, `mismatch`, o `not_read` cuando la identificación no dio nada
que comparar. Una diferencia envía la sesión a revisión, salvo que cambies la
regla `name_mismatch` o `date_of_birth_mismatch`.
## Marca [#marca]
La página que abre un enlace puede llevar tu nombre, tu logo, tu color y un
mensaje breve con tus palabras. Configúralos en **Verify → Configuración**, para
tu equipo o para un proyecto; una sesión con ese `project_id` usa los del
proyecto. Los cambios llegan a los enlaces que ya enviaste.
## Retención y borrado de datos [#retención-y-borrado-de-datos]
Las fotos se borran 30 días después de que termina una sesión. Cámbialo en
**Verify → Configuración**, o por sesión con `options.retention_days` (de 1 a
365). Una sesión que espera tu revisión conserva sus fotos al menos 7 días.
Para borrar antes los datos de una persona, redacta la sesión:
```bash
curl -X POST https://api.upsky.dev/v1/verify/sessions/$SESSION_ID/redact \
-H "Authorization: Bearer $UPSKY_API_KEY"
```
Esto borra de inmediato las fotos, lo que se leyó de la identificación, los
datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los
motivos y las revisiones se quedan como tu registro, y `redacted_at` dice cuándo
pasó. No se puede deshacer, solo funciona en una sesión que ya terminó, y envía
un webhook `verify.session.redacted`.
Todo lo anterior sigue el mismo orden: lo que indica la solicitud, luego la
configuración del proyecto, luego la de tu equipo. Cada sesión conserva las
opciones con las que se creó, devueltas como `options`.
## Estado de la sesión [#estado-de-la-sesión]
| Estado | Significado |
| -------------- | --------------------------------------------------------------------- |
| `pending` | El enlace no se ha abierto |
| `in_progress` | La persona está tomando sus fotos |
| `processing` | Ya se envió; las revisiones están en curso |
| `approved` | Todas las revisiones pasaron |
| `rejected` | Una revisión falló. `reasons` dice cuál |
| `needs_review` | Las revisiones no pudieron decidir. Apruébala o recházala en el panel |
| `expired` | El enlace no se terminó a tiempo. No se cobra |
| `canceled` | La cancelaste. No se cobra |
Una sesión decidida tiene `checks` (prueba de vida, edad, documento,
coincidencia de rostro y, cuando enviaste `expected`, coincidencia de datos, con
sus puntajes) y, en el nivel de identidad,
`verified_outputs`: el nombre, la fecha de nacimiento y los datos del documento
leídos de la identificación.
## Modo de prueba [#modo-de-prueba]
Las sesiones creadas con una llave de prueba recorren todo el flujo con
revisiones simuladas y nunca se cobran. En el último paso, la persona elige el
resultado que quiere, para que pruebes aprobada, rechazada y requiere revisión
sin identificaciones reales.
## Modo real [#modo-real]
Las sesiones creadas con una llave real hacen revisiones reales y se cobran
cuando llegan a una decisión. Las sesiones reales están abiertas para los
niveles `age` e `identity`.
La persona tiene tres intentos para la prueba de vida en cada enlace. Después
del tercero, el enlace ya no puede continuar: crea una sesión nueva para ella.
Un enlace que se queda sin intentos y vence no se cobra.
# Niveles de verificación (/es/verify/tiers)
Upsky Verify tiene tres niveles. Cada uno comprueba más que el anterior.
## Tier 1: Age & Liveness [#tier-1-age--liveness]
Estimación de edad y prueba de presencia.
- El usuario realiza una selfie con prueba de vida.
- La IA estima su rango de edad.
- Devuelve un resultado de riesgo para el umbral de 18 años.
- Los casos cercanos al umbral requieren identificación.
No acredita la edad exacta ni la identidad legal de la persona.
## Tier 2: Identity [#tier-2-identity]
Todo el Tier 1, más identificación oficial.
- Captura de la identificación, frente y reverso.
- Extracción de nombre y fecha de nacimiento.
- Comprobación documental de mayoría de edad.
- Selfie con prueba de vida.
- Comparación facial entre selfie e identificación.
Verifica la coincidencia biométrica y los datos leídos, pero no confirma que
la identificación haya sido emitida realmente por el gobierno.
## Tier 3: Identity Plus [#tier-3-identity-plus]
Verificación documental reforzada y consulta autorizada.
- Todo lo del Tier 2.
- Análisis especializado de autenticidad documental.
- Comprobación de vigencia de la identificación mediante proveedor autorizado.
- Resultado detallado de las comprobaciones ejecutadas.
La consulta de vigencia no es necesariamente una comparación biométrica con
los registros oficiales. Su alcance depende del proveedor y del contrato.
# List sessions (/es/verify/reference/listVerifySessions)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Verify",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"VerifySession": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"verify.session"
]
},
"livemode": {
"type": "boolean"
},
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
]
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
],
"description": "`pending` until the person opens the link, `in_progress` while they take photos, `processing` once submitted, then `approved`, `rejected` or `needs_review`. A link that's never finished ends `expired` or `canceled`, and isn't charged."
},
"url": {
"type": [
"string",
"null"
],
"description": "The link to send the person to. Null once it can't be used anymore.",
"example": "https://verify.upsky.dev/s/9Qm2..."
},
"reference": {
"type": [
"string",
"null"
]
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"project_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"return_url": {
"type": [
"string",
"null"
]
},
"minimum_age": {
"type": "integer"
},
"allow_selfie_upload": {
"type": "boolean",
"description": "Whether the person may send a selfie from their photos. When false, it has to be taken with the camera on the page."
},
"show_result": {
"type": "boolean",
"description": "Si el enlace muestra a la persona el resultado una vez decidido."
},
"thresholds": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": [
"relaxed",
"balanced",
"strict",
"custom"
]
},
"liveness_pass": {
"type": "number"
},
"liveness_review": {
"type": "number"
},
"face_match_pass": {
"type": "number"
},
"face_match_review": {
"type": "number"
},
"age_buffer_years": {
"type": "integer"
}
},
"required": [
"preset",
"liveness_pass",
"liveness_review",
"face_match_pass",
"face_match_review",
"age_buffer_years"
],
"description": "Qué tan segura debía estar cada revisión, copiado al crear la sesión de la configuración de Verify de su proyecto, o de la de tu equipo si el proyecto no tiene. Una puntuación igual o mayor que la marca de aprobación pasa; desde la marca de revisión hasta la de aprobación va a revisión; por debajo de la marca de revisión se rechaza."
},
"options": {
"$ref": "#/components/schemas/VerifySessionOptions"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"liveness_failed",
"liveness_inconclusive",
"face_not_found",
"age_under_minimum",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_mismatch",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch",
"processing_error",
"reviewed"
]
},
"description": "Why the session ended as it did.",
"example": [
"face_mismatch"
]
},
"checks": {
"$ref": "#/components/schemas/VerifyChecks"
},
"document_type": {
"type": [
"string",
"null"
],
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card",
null
]
},
"verified_outputs": {
"$ref": "#/components/schemas/VerifiedOutputs"
},
"expires_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"started_at": {
"type": [
"string",
"null"
]
},
"submitted_at": {
"type": [
"string",
"null"
]
},
"decided_at": {
"type": [
"string",
"null"
]
},
"reviewed_at": {
"type": [
"string",
"null"
]
},
"redacted_at": {
"type": [
"string",
"null"
],
"description": "Cuándo se borraron los datos de la persona de la sesión con el endpoint de redacción."
}
},
"required": [
"id",
"object",
"livemode",
"tier",
"status",
"url",
"reference",
"metadata",
"project_id",
"return_url",
"minimum_age",
"allow_selfie_upload",
"show_result",
"thresholds",
"options",
"reasons",
"checks",
"document_type",
"verified_outputs",
"expires_at",
"created_at",
"started_at",
"submitted_at",
"decided_at",
"reviewed_at",
"redacted_at"
]
},
"VerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país.",
"example": [
"MEX",
"USA"
]
},
"max_attempts": {
"type": "integer",
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página. Una lectura que encuentra una foto borrosa, otro tipo de identificación, una vencida o un país que no aceptas le pide intentarlo de nuevo, hasta que se acaban."
}
},
"required": [
"allowed_types",
"allowed_countries",
"max_attempts"
]
},
"rules": {
"$ref": "#/components/schemas/VerifyRules"
},
"retention_days": {
"type": "integer",
"description": "Días después de que termina la sesión en que se borran sus fotos. Una sesión que espera revisión las conserva al menos 7 días."
}
},
"required": [
"documents",
"rules",
"retention_days"
],
"description": "Con qué opciones corre la sesión, copiadas al crearla de la configuración de Verify de su proyecto, o de tu equipo, más lo que hayas indicado en la solicitud."
},
"VerifyRules": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"required": [
"liveness_inconclusive",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch"
],
"description": "A qué lleva cada problema: rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo (`ignore`). Los problemas que no están aquí siempre rechazan: una prueba de vida fallida, que no haya rostro, un rostro que no es el de la identificación, o una edad menor a la mínima."
},
"VerifyChecks": {
"type": "object",
"properties": {
"liveness": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"confidence": {
"type": [
"number",
"null"
],
"description": "AWS Face Liveness confidence, 0 to 100."
},
"feedback": {
"type": "array",
"items": {
"type": "string",
"enum": [
"face_not_visible",
"face_obstructed",
"face_not_aligned",
"eyes_closed",
"low_lighting",
"high_lighting",
"low_video_quality"
]
},
"description": "Qué bajó la puntuación de la prueba de vida, como `high_lighting`. Vacío si nada la bajó."
}
},
"required": [
"status",
"confidence",
"feedback"
]
},
"age": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"minimum_age": {
"type": "integer"
},
"estimated_low": {
"type": [
"number",
"null"
]
},
"estimated_high": {
"type": [
"number",
"null"
]
},
"estimated_age": {
"type": [
"number",
"null"
],
"description": "La edad que aparenta la persona: el punto medio del rango estimado. En el nivel `age` pasa desde `minimum_age` más tu margen de edad."
},
"age_from_document": {
"type": [
"integer",
"null"
]
}
},
"required": [
"status",
"minimum_age",
"estimated_low",
"estimated_high",
"estimated_age",
"age_from_document"
]
},
"document": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"expired": {
"type": [
"boolean",
"null"
]
},
"checksums_valid": {
"type": [
"boolean",
"null"
]
},
"type_matches": {
"type": [
"boolean",
"null"
],
"description": "Si la identificación es del tipo que la persona eligió. Null cuando la lectura no pudo saberlo."
},
"country_accepted": {
"type": [
"boolean",
"null"
],
"description": "Si el país de la identificación es uno que la sesión acepta. Null cuando se acepta cualquiera, o no se pudo leer el país."
}
},
"required": [
"status",
"document_type",
"expired",
"checksums_valid",
"type_matches",
"country_accepted"
]
},
"face_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"similarity": {
"type": [
"number",
"null"
],
"description": "How alike the selfie and the ID's photo are, 0 to 100."
}
},
"required": [
"status",
"similarity"
]
},
"data_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"first_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"last_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
}
},
"required": [
"status",
"first_name",
"last_name",
"date_of_birth"
],
"description": "Cómo se comparó la identificación con los datos de `expected` que enviaste. Solo en sesiones creadas con ellos. Un campo es null cuando no esperabas nada para él, y `not_read` cuando la identificación no dio nada que comparar."
}
},
"description": "What each check found. Empty until the session is decided."
},
"VerifiedOutputs": {
"type": [
"object",
"null"
],
"properties": {
"first_name": {
"type": [
"string",
"null"
]
},
"last_name": {
"type": [
"string",
"null"
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"example": "1992-04-15"
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"document_number": {
"type": [
"string",
"null"
]
},
"issuing_country": {
"type": [
"string",
"null"
],
"example": "MEX"
},
"expires_on": {
"type": [
"string",
"null"
],
"example": "2031-12-31"
}
},
"required": [
"first_name",
"last_name",
"date_of_birth",
"document_type",
"document_number",
"issuing_country",
"expires_on"
],
"description": "What was read from the ID. Identity tier and above, once decided. Only returned when you read one session, never in lists or webhooks."
},
"CreateVerifySession": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
],
"default": "identity",
"description": "`age`: selfie con prueba de vida y una estimación de edad. `identity`: agrega una identificación, por ambos lados, y la comparación de rostro. `identity_plus` aún no está disponible."
},
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Your own ID for this person. Only your team sees it.",
"example": "user_123"
},
"return_url": {
"type": "string",
"maxLength": 2000,
"description": "Where to send the person once they're done: a web page, or your app through its own scheme (`myapp://verified`) to bring them back from an in-app browser. The page goes there by itself a few seconds after they submit, or after the result when `show_result` is on, and also shows a button.",
"example": "https://example.com/verified"
},
"minimum_age": {
"type": "integer",
"minimum": 13,
"maximum": 25,
"default": 18
},
"allow_selfie_upload": {
"type": "boolean",
"default": false,
"description": "Let the person send a selfie from their photos instead of taking it with the camera. Camera only by default. Live sessions always take the selfie with the camera, through the liveness check."
},
"show_result": {
"type": "boolean",
"default": false,
"description": "Muestra a la persona el resultado en el enlace justo después de enviarlo: aprobada, rechazada o en revisión, nunca el motivo. Apagado por defecto, así que tú le avisas."
},
"expires_in": {
"type": "integer",
"minimum": 3600,
"maximum": 604800,
"default": 86400,
"description": "Seconds the link works for, from an hour to seven days. A day by default."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Uno de los proyectos de tu equipo, para agrupar sesiones. La sesión usa la configuración de Verify del proyecto donde tenga la suya."
},
"options": {
"$ref": "#/components/schemas/CreateVerifySessionOptions"
},
"expected": {
"$ref": "#/components/schemas/VerifyExpectedData"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string",
"maxLength": 500
},
"description": "Up to 20 keys of your own, returned as given."
}
}
},
"CreateVerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"minItems": 1,
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z]{3}$",
"example": "MEX"
},
"maxItems": 250,
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país."
},
"max_attempts": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página, de 1 a 5."
}
},
"additionalProperties": false
},
"rules": {
"$ref": "#/components/schemas/VerifyRuleOverrides"
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Días después de que termina la sesión en que se borran sus fotos, de 1 a 365."
}
},
"additionalProperties": false,
"description": "Lo que esta sesión debe hacer distinto de la configuración de Verify de su proyecto, o de tu equipo. Omite una clave para conservar la configuración."
},
"VerifyRuleOverrides": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"additionalProperties": false
},
"VerifyExpectedData": {
"type": "object",
"properties": {
"first_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"last_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"date_of_birth": {
"type": "string",
"format": "date",
"example": "1992-04-15"
}
},
"additionalProperties": false,
"description": "Lo que esperas que diga la identificación. Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres. Solo en el nivel `identity`. El resultado está en `checks.data_match`; de forma predeterminada, una diferencia envía la sesión a revisión."
},
"VerifySessionList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VerifySession"
}
},
"has_more": {
"type": "boolean"
}
},
"required": [
"object",
"data",
"has_more"
]
}
},
"parameters": {}
},
"paths": {
"/v1/verify/sessions": {
"post": {
"operationId": "createVerifySession",
"tags": [
"Verify"
],
"summary": "Crear una sesión",
"description": "Starts a verification and returns the `url` to send the person to. A live key sets the tier's price aside, which is kept only if the session reaches a decision.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateVerifySession"
}
}
}
},
"responses": {
"201": {
"description": "La nueva sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"400": {
"description": "Something in the request isn't valid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "Not enough credits.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Live keys need Upsky AI Pro.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "Las verificaciones reales no están conectadas en este momento, o no se pudieron leer los precios.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"operationId": "listVerifySessions",
"tags": [
"Verify"
],
"summary": "List sessions",
"description": "Your team's sessions in the key's mode, newest first. Pass the last `id` as `starting_after` for the next page.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": false,
"name": "starting_after",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
]
},
"required": false,
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "The sessions.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySessionList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}": {
"get": {
"operationId": "getVerifySession",
"tags": [
"Verify"
],
"summary": "Obtener una sesión",
"description": "Returns a session, with what was read from the ID once it's decided.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/cancel": {
"post": {
"operationId": "cancelVerifySession",
"tags": [
"Verify"
],
"summary": "Cancel a session",
"description": "Stops a session the person hasn't submitted. Its link stops working and nothing is charged.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "The canceled session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "The session was already submitted or ended.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/redact": {
"post": {
"operationId": "redactVerifySession",
"tags": [
"Verify"
],
"summary": "Redactar una sesión",
"description": "Borra de inmediato todo sobre la persona de una sesión que ya terminó: las fotos, lo que se leyó de la identificación, los datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los motivos y las revisiones se quedan como tu registro. No se puede deshacer. Redactar una sesión dos veces no cambia nada.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión redactada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "La sesión no ha terminado. Cancélala primero, o espera su resultado.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Crear una sesión (/es/verify/reference/createVerifySession)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Verify",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"VerifySession": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"verify.session"
]
},
"livemode": {
"type": "boolean"
},
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
]
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
],
"description": "`pending` until the person opens the link, `in_progress` while they take photos, `processing` once submitted, then `approved`, `rejected` or `needs_review`. A link that's never finished ends `expired` or `canceled`, and isn't charged."
},
"url": {
"type": [
"string",
"null"
],
"description": "The link to send the person to. Null once it can't be used anymore.",
"example": "https://verify.upsky.dev/s/9Qm2..."
},
"reference": {
"type": [
"string",
"null"
]
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"project_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"return_url": {
"type": [
"string",
"null"
]
},
"minimum_age": {
"type": "integer"
},
"allow_selfie_upload": {
"type": "boolean",
"description": "Whether the person may send a selfie from their photos. When false, it has to be taken with the camera on the page."
},
"show_result": {
"type": "boolean",
"description": "Si el enlace muestra a la persona el resultado una vez decidido."
},
"thresholds": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": [
"relaxed",
"balanced",
"strict",
"custom"
]
},
"liveness_pass": {
"type": "number"
},
"liveness_review": {
"type": "number"
},
"face_match_pass": {
"type": "number"
},
"face_match_review": {
"type": "number"
},
"age_buffer_years": {
"type": "integer"
}
},
"required": [
"preset",
"liveness_pass",
"liveness_review",
"face_match_pass",
"face_match_review",
"age_buffer_years"
],
"description": "Qué tan segura debía estar cada revisión, copiado al crear la sesión de la configuración de Verify de su proyecto, o de la de tu equipo si el proyecto no tiene. Una puntuación igual o mayor que la marca de aprobación pasa; desde la marca de revisión hasta la de aprobación va a revisión; por debajo de la marca de revisión se rechaza."
},
"options": {
"$ref": "#/components/schemas/VerifySessionOptions"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"liveness_failed",
"liveness_inconclusive",
"face_not_found",
"age_under_minimum",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_mismatch",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch",
"processing_error",
"reviewed"
]
},
"description": "Why the session ended as it did.",
"example": [
"face_mismatch"
]
},
"checks": {
"$ref": "#/components/schemas/VerifyChecks"
},
"document_type": {
"type": [
"string",
"null"
],
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card",
null
]
},
"verified_outputs": {
"$ref": "#/components/schemas/VerifiedOutputs"
},
"expires_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"started_at": {
"type": [
"string",
"null"
]
},
"submitted_at": {
"type": [
"string",
"null"
]
},
"decided_at": {
"type": [
"string",
"null"
]
},
"reviewed_at": {
"type": [
"string",
"null"
]
},
"redacted_at": {
"type": [
"string",
"null"
],
"description": "Cuándo se borraron los datos de la persona de la sesión con el endpoint de redacción."
}
},
"required": [
"id",
"object",
"livemode",
"tier",
"status",
"url",
"reference",
"metadata",
"project_id",
"return_url",
"minimum_age",
"allow_selfie_upload",
"show_result",
"thresholds",
"options",
"reasons",
"checks",
"document_type",
"verified_outputs",
"expires_at",
"created_at",
"started_at",
"submitted_at",
"decided_at",
"reviewed_at",
"redacted_at"
]
},
"VerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país.",
"example": [
"MEX",
"USA"
]
},
"max_attempts": {
"type": "integer",
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página. Una lectura que encuentra una foto borrosa, otro tipo de identificación, una vencida o un país que no aceptas le pide intentarlo de nuevo, hasta que se acaban."
}
},
"required": [
"allowed_types",
"allowed_countries",
"max_attempts"
]
},
"rules": {
"$ref": "#/components/schemas/VerifyRules"
},
"retention_days": {
"type": "integer",
"description": "Días después de que termina la sesión en que se borran sus fotos. Una sesión que espera revisión las conserva al menos 7 días."
}
},
"required": [
"documents",
"rules",
"retention_days"
],
"description": "Con qué opciones corre la sesión, copiadas al crearla de la configuración de Verify de su proyecto, o de tu equipo, más lo que hayas indicado en la solicitud."
},
"VerifyRules": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"required": [
"liveness_inconclusive",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch"
],
"description": "A qué lleva cada problema: rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo (`ignore`). Los problemas que no están aquí siempre rechazan: una prueba de vida fallida, que no haya rostro, un rostro que no es el de la identificación, o una edad menor a la mínima."
},
"VerifyChecks": {
"type": "object",
"properties": {
"liveness": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"confidence": {
"type": [
"number",
"null"
],
"description": "AWS Face Liveness confidence, 0 to 100."
},
"feedback": {
"type": "array",
"items": {
"type": "string",
"enum": [
"face_not_visible",
"face_obstructed",
"face_not_aligned",
"eyes_closed",
"low_lighting",
"high_lighting",
"low_video_quality"
]
},
"description": "Qué bajó la puntuación de la prueba de vida, como `high_lighting`. Vacío si nada la bajó."
}
},
"required": [
"status",
"confidence",
"feedback"
]
},
"age": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"minimum_age": {
"type": "integer"
},
"estimated_low": {
"type": [
"number",
"null"
]
},
"estimated_high": {
"type": [
"number",
"null"
]
},
"estimated_age": {
"type": [
"number",
"null"
],
"description": "La edad que aparenta la persona: el punto medio del rango estimado. En el nivel `age` pasa desde `minimum_age` más tu margen de edad."
},
"age_from_document": {
"type": [
"integer",
"null"
]
}
},
"required": [
"status",
"minimum_age",
"estimated_low",
"estimated_high",
"estimated_age",
"age_from_document"
]
},
"document": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"expired": {
"type": [
"boolean",
"null"
]
},
"checksums_valid": {
"type": [
"boolean",
"null"
]
},
"type_matches": {
"type": [
"boolean",
"null"
],
"description": "Si la identificación es del tipo que la persona eligió. Null cuando la lectura no pudo saberlo."
},
"country_accepted": {
"type": [
"boolean",
"null"
],
"description": "Si el país de la identificación es uno que la sesión acepta. Null cuando se acepta cualquiera, o no se pudo leer el país."
}
},
"required": [
"status",
"document_type",
"expired",
"checksums_valid",
"type_matches",
"country_accepted"
]
},
"face_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"similarity": {
"type": [
"number",
"null"
],
"description": "How alike the selfie and the ID's photo are, 0 to 100."
}
},
"required": [
"status",
"similarity"
]
},
"data_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"first_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"last_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
}
},
"required": [
"status",
"first_name",
"last_name",
"date_of_birth"
],
"description": "Cómo se comparó la identificación con los datos de `expected` que enviaste. Solo en sesiones creadas con ellos. Un campo es null cuando no esperabas nada para él, y `not_read` cuando la identificación no dio nada que comparar."
}
},
"description": "What each check found. Empty until the session is decided."
},
"VerifiedOutputs": {
"type": [
"object",
"null"
],
"properties": {
"first_name": {
"type": [
"string",
"null"
]
},
"last_name": {
"type": [
"string",
"null"
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"example": "1992-04-15"
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"document_number": {
"type": [
"string",
"null"
]
},
"issuing_country": {
"type": [
"string",
"null"
],
"example": "MEX"
},
"expires_on": {
"type": [
"string",
"null"
],
"example": "2031-12-31"
}
},
"required": [
"first_name",
"last_name",
"date_of_birth",
"document_type",
"document_number",
"issuing_country",
"expires_on"
],
"description": "What was read from the ID. Identity tier and above, once decided. Only returned when you read one session, never in lists or webhooks."
},
"CreateVerifySession": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
],
"default": "identity",
"description": "`age`: selfie con prueba de vida y una estimación de edad. `identity`: agrega una identificación, por ambos lados, y la comparación de rostro. `identity_plus` aún no está disponible."
},
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Your own ID for this person. Only your team sees it.",
"example": "user_123"
},
"return_url": {
"type": "string",
"maxLength": 2000,
"description": "Where to send the person once they're done: a web page, or your app through its own scheme (`myapp://verified`) to bring them back from an in-app browser. The page goes there by itself a few seconds after they submit, or after the result when `show_result` is on, and also shows a button.",
"example": "https://example.com/verified"
},
"minimum_age": {
"type": "integer",
"minimum": 13,
"maximum": 25,
"default": 18
},
"allow_selfie_upload": {
"type": "boolean",
"default": false,
"description": "Let the person send a selfie from their photos instead of taking it with the camera. Camera only by default. Live sessions always take the selfie with the camera, through the liveness check."
},
"show_result": {
"type": "boolean",
"default": false,
"description": "Muestra a la persona el resultado en el enlace justo después de enviarlo: aprobada, rechazada o en revisión, nunca el motivo. Apagado por defecto, así que tú le avisas."
},
"expires_in": {
"type": "integer",
"minimum": 3600,
"maximum": 604800,
"default": 86400,
"description": "Seconds the link works for, from an hour to seven days. A day by default."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Uno de los proyectos de tu equipo, para agrupar sesiones. La sesión usa la configuración de Verify del proyecto donde tenga la suya."
},
"options": {
"$ref": "#/components/schemas/CreateVerifySessionOptions"
},
"expected": {
"$ref": "#/components/schemas/VerifyExpectedData"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string",
"maxLength": 500
},
"description": "Up to 20 keys of your own, returned as given."
}
}
},
"CreateVerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"minItems": 1,
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z]{3}$",
"example": "MEX"
},
"maxItems": 250,
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país."
},
"max_attempts": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página, de 1 a 5."
}
},
"additionalProperties": false
},
"rules": {
"$ref": "#/components/schemas/VerifyRuleOverrides"
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Días después de que termina la sesión en que se borran sus fotos, de 1 a 365."
}
},
"additionalProperties": false,
"description": "Lo que esta sesión debe hacer distinto de la configuración de Verify de su proyecto, o de tu equipo. Omite una clave para conservar la configuración."
},
"VerifyRuleOverrides": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"additionalProperties": false
},
"VerifyExpectedData": {
"type": "object",
"properties": {
"first_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"last_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"date_of_birth": {
"type": "string",
"format": "date",
"example": "1992-04-15"
}
},
"additionalProperties": false,
"description": "Lo que esperas que diga la identificación. Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres. Solo en el nivel `identity`. El resultado está en `checks.data_match`; de forma predeterminada, una diferencia envía la sesión a revisión."
},
"VerifySessionList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VerifySession"
}
},
"has_more": {
"type": "boolean"
}
},
"required": [
"object",
"data",
"has_more"
]
}
},
"parameters": {}
},
"paths": {
"/v1/verify/sessions": {
"post": {
"operationId": "createVerifySession",
"tags": [
"Verify"
],
"summary": "Crear una sesión",
"description": "Starts a verification and returns the `url` to send the person to. A live key sets the tier's price aside, which is kept only if the session reaches a decision.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateVerifySession"
}
}
}
},
"responses": {
"201": {
"description": "La nueva sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"400": {
"description": "Something in the request isn't valid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "Not enough credits.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Live keys need Upsky AI Pro.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "Las verificaciones reales no están conectadas en este momento, o no se pudieron leer los precios.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"operationId": "listVerifySessions",
"tags": [
"Verify"
],
"summary": "List sessions",
"description": "Your team's sessions in the key's mode, newest first. Pass the last `id` as `starting_after` for the next page.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": false,
"name": "starting_after",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
]
},
"required": false,
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "The sessions.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySessionList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}": {
"get": {
"operationId": "getVerifySession",
"tags": [
"Verify"
],
"summary": "Obtener una sesión",
"description": "Returns a session, with what was read from the ID once it's decided.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/cancel": {
"post": {
"operationId": "cancelVerifySession",
"tags": [
"Verify"
],
"summary": "Cancel a session",
"description": "Stops a session the person hasn't submitted. Its link stops working and nothing is charged.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "The canceled session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "The session was already submitted or ended.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/redact": {
"post": {
"operationId": "redactVerifySession",
"tags": [
"Verify"
],
"summary": "Redactar una sesión",
"description": "Borra de inmediato todo sobre la persona de una sesión que ya terminó: las fotos, lo que se leyó de la identificación, los datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los motivos y las revisiones se quedan como tu registro. No se puede deshacer. Redactar una sesión dos veces no cambia nada.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión redactada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "La sesión no ha terminado. Cancélala primero, o espera su resultado.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Obtener una sesión (/es/verify/reference/getVerifySession)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Verify",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"VerifySession": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"verify.session"
]
},
"livemode": {
"type": "boolean"
},
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
]
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
],
"description": "`pending` until the person opens the link, `in_progress` while they take photos, `processing` once submitted, then `approved`, `rejected` or `needs_review`. A link that's never finished ends `expired` or `canceled`, and isn't charged."
},
"url": {
"type": [
"string",
"null"
],
"description": "The link to send the person to. Null once it can't be used anymore.",
"example": "https://verify.upsky.dev/s/9Qm2..."
},
"reference": {
"type": [
"string",
"null"
]
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"project_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"return_url": {
"type": [
"string",
"null"
]
},
"minimum_age": {
"type": "integer"
},
"allow_selfie_upload": {
"type": "boolean",
"description": "Whether the person may send a selfie from their photos. When false, it has to be taken with the camera on the page."
},
"show_result": {
"type": "boolean",
"description": "Si el enlace muestra a la persona el resultado una vez decidido."
},
"thresholds": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": [
"relaxed",
"balanced",
"strict",
"custom"
]
},
"liveness_pass": {
"type": "number"
},
"liveness_review": {
"type": "number"
},
"face_match_pass": {
"type": "number"
},
"face_match_review": {
"type": "number"
},
"age_buffer_years": {
"type": "integer"
}
},
"required": [
"preset",
"liveness_pass",
"liveness_review",
"face_match_pass",
"face_match_review",
"age_buffer_years"
],
"description": "Qué tan segura debía estar cada revisión, copiado al crear la sesión de la configuración de Verify de su proyecto, o de la de tu equipo si el proyecto no tiene. Una puntuación igual o mayor que la marca de aprobación pasa; desde la marca de revisión hasta la de aprobación va a revisión; por debajo de la marca de revisión se rechaza."
},
"options": {
"$ref": "#/components/schemas/VerifySessionOptions"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"liveness_failed",
"liveness_inconclusive",
"face_not_found",
"age_under_minimum",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_mismatch",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch",
"processing_error",
"reviewed"
]
},
"description": "Why the session ended as it did.",
"example": [
"face_mismatch"
]
},
"checks": {
"$ref": "#/components/schemas/VerifyChecks"
},
"document_type": {
"type": [
"string",
"null"
],
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card",
null
]
},
"verified_outputs": {
"$ref": "#/components/schemas/VerifiedOutputs"
},
"expires_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"started_at": {
"type": [
"string",
"null"
]
},
"submitted_at": {
"type": [
"string",
"null"
]
},
"decided_at": {
"type": [
"string",
"null"
]
},
"reviewed_at": {
"type": [
"string",
"null"
]
},
"redacted_at": {
"type": [
"string",
"null"
],
"description": "Cuándo se borraron los datos de la persona de la sesión con el endpoint de redacción."
}
},
"required": [
"id",
"object",
"livemode",
"tier",
"status",
"url",
"reference",
"metadata",
"project_id",
"return_url",
"minimum_age",
"allow_selfie_upload",
"show_result",
"thresholds",
"options",
"reasons",
"checks",
"document_type",
"verified_outputs",
"expires_at",
"created_at",
"started_at",
"submitted_at",
"decided_at",
"reviewed_at",
"redacted_at"
]
},
"VerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país.",
"example": [
"MEX",
"USA"
]
},
"max_attempts": {
"type": "integer",
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página. Una lectura que encuentra una foto borrosa, otro tipo de identificación, una vencida o un país que no aceptas le pide intentarlo de nuevo, hasta que se acaban."
}
},
"required": [
"allowed_types",
"allowed_countries",
"max_attempts"
]
},
"rules": {
"$ref": "#/components/schemas/VerifyRules"
},
"retention_days": {
"type": "integer",
"description": "Días después de que termina la sesión en que se borran sus fotos. Una sesión que espera revisión las conserva al menos 7 días."
}
},
"required": [
"documents",
"rules",
"retention_days"
],
"description": "Con qué opciones corre la sesión, copiadas al crearla de la configuración de Verify de su proyecto, o de tu equipo, más lo que hayas indicado en la solicitud."
},
"VerifyRules": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"required": [
"liveness_inconclusive",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch"
],
"description": "A qué lleva cada problema: rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo (`ignore`). Los problemas que no están aquí siempre rechazan: una prueba de vida fallida, que no haya rostro, un rostro que no es el de la identificación, o una edad menor a la mínima."
},
"VerifyChecks": {
"type": "object",
"properties": {
"liveness": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"confidence": {
"type": [
"number",
"null"
],
"description": "AWS Face Liveness confidence, 0 to 100."
},
"feedback": {
"type": "array",
"items": {
"type": "string",
"enum": [
"face_not_visible",
"face_obstructed",
"face_not_aligned",
"eyes_closed",
"low_lighting",
"high_lighting",
"low_video_quality"
]
},
"description": "Qué bajó la puntuación de la prueba de vida, como `high_lighting`. Vacío si nada la bajó."
}
},
"required": [
"status",
"confidence",
"feedback"
]
},
"age": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"minimum_age": {
"type": "integer"
},
"estimated_low": {
"type": [
"number",
"null"
]
},
"estimated_high": {
"type": [
"number",
"null"
]
},
"estimated_age": {
"type": [
"number",
"null"
],
"description": "La edad que aparenta la persona: el punto medio del rango estimado. En el nivel `age` pasa desde `minimum_age` más tu margen de edad."
},
"age_from_document": {
"type": [
"integer",
"null"
]
}
},
"required": [
"status",
"minimum_age",
"estimated_low",
"estimated_high",
"estimated_age",
"age_from_document"
]
},
"document": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"expired": {
"type": [
"boolean",
"null"
]
},
"checksums_valid": {
"type": [
"boolean",
"null"
]
},
"type_matches": {
"type": [
"boolean",
"null"
],
"description": "Si la identificación es del tipo que la persona eligió. Null cuando la lectura no pudo saberlo."
},
"country_accepted": {
"type": [
"boolean",
"null"
],
"description": "Si el país de la identificación es uno que la sesión acepta. Null cuando se acepta cualquiera, o no se pudo leer el país."
}
},
"required": [
"status",
"document_type",
"expired",
"checksums_valid",
"type_matches",
"country_accepted"
]
},
"face_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"similarity": {
"type": [
"number",
"null"
],
"description": "How alike the selfie and the ID's photo are, 0 to 100."
}
},
"required": [
"status",
"similarity"
]
},
"data_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"first_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"last_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
}
},
"required": [
"status",
"first_name",
"last_name",
"date_of_birth"
],
"description": "Cómo se comparó la identificación con los datos de `expected` que enviaste. Solo en sesiones creadas con ellos. Un campo es null cuando no esperabas nada para él, y `not_read` cuando la identificación no dio nada que comparar."
}
},
"description": "What each check found. Empty until the session is decided."
},
"VerifiedOutputs": {
"type": [
"object",
"null"
],
"properties": {
"first_name": {
"type": [
"string",
"null"
]
},
"last_name": {
"type": [
"string",
"null"
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"example": "1992-04-15"
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"document_number": {
"type": [
"string",
"null"
]
},
"issuing_country": {
"type": [
"string",
"null"
],
"example": "MEX"
},
"expires_on": {
"type": [
"string",
"null"
],
"example": "2031-12-31"
}
},
"required": [
"first_name",
"last_name",
"date_of_birth",
"document_type",
"document_number",
"issuing_country",
"expires_on"
],
"description": "What was read from the ID. Identity tier and above, once decided. Only returned when you read one session, never in lists or webhooks."
},
"CreateVerifySession": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
],
"default": "identity",
"description": "`age`: selfie con prueba de vida y una estimación de edad. `identity`: agrega una identificación, por ambos lados, y la comparación de rostro. `identity_plus` aún no está disponible."
},
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Your own ID for this person. Only your team sees it.",
"example": "user_123"
},
"return_url": {
"type": "string",
"maxLength": 2000,
"description": "Where to send the person once they're done: a web page, or your app through its own scheme (`myapp://verified`) to bring them back from an in-app browser. The page goes there by itself a few seconds after they submit, or after the result when `show_result` is on, and also shows a button.",
"example": "https://example.com/verified"
},
"minimum_age": {
"type": "integer",
"minimum": 13,
"maximum": 25,
"default": 18
},
"allow_selfie_upload": {
"type": "boolean",
"default": false,
"description": "Let the person send a selfie from their photos instead of taking it with the camera. Camera only by default. Live sessions always take the selfie with the camera, through the liveness check."
},
"show_result": {
"type": "boolean",
"default": false,
"description": "Muestra a la persona el resultado en el enlace justo después de enviarlo: aprobada, rechazada o en revisión, nunca el motivo. Apagado por defecto, así que tú le avisas."
},
"expires_in": {
"type": "integer",
"minimum": 3600,
"maximum": 604800,
"default": 86400,
"description": "Seconds the link works for, from an hour to seven days. A day by default."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Uno de los proyectos de tu equipo, para agrupar sesiones. La sesión usa la configuración de Verify del proyecto donde tenga la suya."
},
"options": {
"$ref": "#/components/schemas/CreateVerifySessionOptions"
},
"expected": {
"$ref": "#/components/schemas/VerifyExpectedData"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string",
"maxLength": 500
},
"description": "Up to 20 keys of your own, returned as given."
}
}
},
"CreateVerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"minItems": 1,
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z]{3}$",
"example": "MEX"
},
"maxItems": 250,
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país."
},
"max_attempts": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página, de 1 a 5."
}
},
"additionalProperties": false
},
"rules": {
"$ref": "#/components/schemas/VerifyRuleOverrides"
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Días después de que termina la sesión en que se borran sus fotos, de 1 a 365."
}
},
"additionalProperties": false,
"description": "Lo que esta sesión debe hacer distinto de la configuración de Verify de su proyecto, o de tu equipo. Omite una clave para conservar la configuración."
},
"VerifyRuleOverrides": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"additionalProperties": false
},
"VerifyExpectedData": {
"type": "object",
"properties": {
"first_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"last_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"date_of_birth": {
"type": "string",
"format": "date",
"example": "1992-04-15"
}
},
"additionalProperties": false,
"description": "Lo que esperas que diga la identificación. Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres. Solo en el nivel `identity`. El resultado está en `checks.data_match`; de forma predeterminada, una diferencia envía la sesión a revisión."
},
"VerifySessionList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VerifySession"
}
},
"has_more": {
"type": "boolean"
}
},
"required": [
"object",
"data",
"has_more"
]
}
},
"parameters": {}
},
"paths": {
"/v1/verify/sessions": {
"post": {
"operationId": "createVerifySession",
"tags": [
"Verify"
],
"summary": "Crear una sesión",
"description": "Starts a verification and returns the `url` to send the person to. A live key sets the tier's price aside, which is kept only if the session reaches a decision.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateVerifySession"
}
}
}
},
"responses": {
"201": {
"description": "La nueva sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"400": {
"description": "Something in the request isn't valid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "Not enough credits.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Live keys need Upsky AI Pro.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "Las verificaciones reales no están conectadas en este momento, o no se pudieron leer los precios.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"operationId": "listVerifySessions",
"tags": [
"Verify"
],
"summary": "List sessions",
"description": "Your team's sessions in the key's mode, newest first. Pass the last `id` as `starting_after` for the next page.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": false,
"name": "starting_after",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
]
},
"required": false,
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "The sessions.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySessionList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}": {
"get": {
"operationId": "getVerifySession",
"tags": [
"Verify"
],
"summary": "Obtener una sesión",
"description": "Returns a session, with what was read from the ID once it's decided.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/cancel": {
"post": {
"operationId": "cancelVerifySession",
"tags": [
"Verify"
],
"summary": "Cancel a session",
"description": "Stops a session the person hasn't submitted. Its link stops working and nothing is charged.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "The canceled session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "The session was already submitted or ended.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/redact": {
"post": {
"operationId": "redactVerifySession",
"tags": [
"Verify"
],
"summary": "Redactar una sesión",
"description": "Borra de inmediato todo sobre la persona de una sesión que ya terminó: las fotos, lo que se leyó de la identificación, los datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los motivos y las revisiones se quedan como tu registro. No se puede deshacer. Redactar una sesión dos veces no cambia nada.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión redactada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "La sesión no ha terminado. Cancélala primero, o espera su resultado.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Cancel a session (/es/verify/reference/cancelVerifySession)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Verify",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"VerifySession": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"verify.session"
]
},
"livemode": {
"type": "boolean"
},
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
]
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
],
"description": "`pending` until the person opens the link, `in_progress` while they take photos, `processing` once submitted, then `approved`, `rejected` or `needs_review`. A link that's never finished ends `expired` or `canceled`, and isn't charged."
},
"url": {
"type": [
"string",
"null"
],
"description": "The link to send the person to. Null once it can't be used anymore.",
"example": "https://verify.upsky.dev/s/9Qm2..."
},
"reference": {
"type": [
"string",
"null"
]
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"project_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"return_url": {
"type": [
"string",
"null"
]
},
"minimum_age": {
"type": "integer"
},
"allow_selfie_upload": {
"type": "boolean",
"description": "Whether the person may send a selfie from their photos. When false, it has to be taken with the camera on the page."
},
"show_result": {
"type": "boolean",
"description": "Si el enlace muestra a la persona el resultado una vez decidido."
},
"thresholds": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": [
"relaxed",
"balanced",
"strict",
"custom"
]
},
"liveness_pass": {
"type": "number"
},
"liveness_review": {
"type": "number"
},
"face_match_pass": {
"type": "number"
},
"face_match_review": {
"type": "number"
},
"age_buffer_years": {
"type": "integer"
}
},
"required": [
"preset",
"liveness_pass",
"liveness_review",
"face_match_pass",
"face_match_review",
"age_buffer_years"
],
"description": "Qué tan segura debía estar cada revisión, copiado al crear la sesión de la configuración de Verify de su proyecto, o de la de tu equipo si el proyecto no tiene. Una puntuación igual o mayor que la marca de aprobación pasa; desde la marca de revisión hasta la de aprobación va a revisión; por debajo de la marca de revisión se rechaza."
},
"options": {
"$ref": "#/components/schemas/VerifySessionOptions"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"liveness_failed",
"liveness_inconclusive",
"face_not_found",
"age_under_minimum",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_mismatch",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch",
"processing_error",
"reviewed"
]
},
"description": "Why the session ended as it did.",
"example": [
"face_mismatch"
]
},
"checks": {
"$ref": "#/components/schemas/VerifyChecks"
},
"document_type": {
"type": [
"string",
"null"
],
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card",
null
]
},
"verified_outputs": {
"$ref": "#/components/schemas/VerifiedOutputs"
},
"expires_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"started_at": {
"type": [
"string",
"null"
]
},
"submitted_at": {
"type": [
"string",
"null"
]
},
"decided_at": {
"type": [
"string",
"null"
]
},
"reviewed_at": {
"type": [
"string",
"null"
]
},
"redacted_at": {
"type": [
"string",
"null"
],
"description": "Cuándo se borraron los datos de la persona de la sesión con el endpoint de redacción."
}
},
"required": [
"id",
"object",
"livemode",
"tier",
"status",
"url",
"reference",
"metadata",
"project_id",
"return_url",
"minimum_age",
"allow_selfie_upload",
"show_result",
"thresholds",
"options",
"reasons",
"checks",
"document_type",
"verified_outputs",
"expires_at",
"created_at",
"started_at",
"submitted_at",
"decided_at",
"reviewed_at",
"redacted_at"
]
},
"VerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país.",
"example": [
"MEX",
"USA"
]
},
"max_attempts": {
"type": "integer",
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página. Una lectura que encuentra una foto borrosa, otro tipo de identificación, una vencida o un país que no aceptas le pide intentarlo de nuevo, hasta que se acaban."
}
},
"required": [
"allowed_types",
"allowed_countries",
"max_attempts"
]
},
"rules": {
"$ref": "#/components/schemas/VerifyRules"
},
"retention_days": {
"type": "integer",
"description": "Días después de que termina la sesión en que se borran sus fotos. Una sesión que espera revisión las conserva al menos 7 días."
}
},
"required": [
"documents",
"rules",
"retention_days"
],
"description": "Con qué opciones corre la sesión, copiadas al crearla de la configuración de Verify de su proyecto, o de tu equipo, más lo que hayas indicado en la solicitud."
},
"VerifyRules": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"required": [
"liveness_inconclusive",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch"
],
"description": "A qué lleva cada problema: rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo (`ignore`). Los problemas que no están aquí siempre rechazan: una prueba de vida fallida, que no haya rostro, un rostro que no es el de la identificación, o una edad menor a la mínima."
},
"VerifyChecks": {
"type": "object",
"properties": {
"liveness": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"confidence": {
"type": [
"number",
"null"
],
"description": "AWS Face Liveness confidence, 0 to 100."
},
"feedback": {
"type": "array",
"items": {
"type": "string",
"enum": [
"face_not_visible",
"face_obstructed",
"face_not_aligned",
"eyes_closed",
"low_lighting",
"high_lighting",
"low_video_quality"
]
},
"description": "Qué bajó la puntuación de la prueba de vida, como `high_lighting`. Vacío si nada la bajó."
}
},
"required": [
"status",
"confidence",
"feedback"
]
},
"age": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"minimum_age": {
"type": "integer"
},
"estimated_low": {
"type": [
"number",
"null"
]
},
"estimated_high": {
"type": [
"number",
"null"
]
},
"estimated_age": {
"type": [
"number",
"null"
],
"description": "La edad que aparenta la persona: el punto medio del rango estimado. En el nivel `age` pasa desde `minimum_age` más tu margen de edad."
},
"age_from_document": {
"type": [
"integer",
"null"
]
}
},
"required": [
"status",
"minimum_age",
"estimated_low",
"estimated_high",
"estimated_age",
"age_from_document"
]
},
"document": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"expired": {
"type": [
"boolean",
"null"
]
},
"checksums_valid": {
"type": [
"boolean",
"null"
]
},
"type_matches": {
"type": [
"boolean",
"null"
],
"description": "Si la identificación es del tipo que la persona eligió. Null cuando la lectura no pudo saberlo."
},
"country_accepted": {
"type": [
"boolean",
"null"
],
"description": "Si el país de la identificación es uno que la sesión acepta. Null cuando se acepta cualquiera, o no se pudo leer el país."
}
},
"required": [
"status",
"document_type",
"expired",
"checksums_valid",
"type_matches",
"country_accepted"
]
},
"face_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"similarity": {
"type": [
"number",
"null"
],
"description": "How alike the selfie and the ID's photo are, 0 to 100."
}
},
"required": [
"status",
"similarity"
]
},
"data_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"first_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"last_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
}
},
"required": [
"status",
"first_name",
"last_name",
"date_of_birth"
],
"description": "Cómo se comparó la identificación con los datos de `expected` que enviaste. Solo en sesiones creadas con ellos. Un campo es null cuando no esperabas nada para él, y `not_read` cuando la identificación no dio nada que comparar."
}
},
"description": "What each check found. Empty until the session is decided."
},
"VerifiedOutputs": {
"type": [
"object",
"null"
],
"properties": {
"first_name": {
"type": [
"string",
"null"
]
},
"last_name": {
"type": [
"string",
"null"
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"example": "1992-04-15"
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"document_number": {
"type": [
"string",
"null"
]
},
"issuing_country": {
"type": [
"string",
"null"
],
"example": "MEX"
},
"expires_on": {
"type": [
"string",
"null"
],
"example": "2031-12-31"
}
},
"required": [
"first_name",
"last_name",
"date_of_birth",
"document_type",
"document_number",
"issuing_country",
"expires_on"
],
"description": "What was read from the ID. Identity tier and above, once decided. Only returned when you read one session, never in lists or webhooks."
},
"CreateVerifySession": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
],
"default": "identity",
"description": "`age`: selfie con prueba de vida y una estimación de edad. `identity`: agrega una identificación, por ambos lados, y la comparación de rostro. `identity_plus` aún no está disponible."
},
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Your own ID for this person. Only your team sees it.",
"example": "user_123"
},
"return_url": {
"type": "string",
"maxLength": 2000,
"description": "Where to send the person once they're done: a web page, or your app through its own scheme (`myapp://verified`) to bring them back from an in-app browser. The page goes there by itself a few seconds after they submit, or after the result when `show_result` is on, and also shows a button.",
"example": "https://example.com/verified"
},
"minimum_age": {
"type": "integer",
"minimum": 13,
"maximum": 25,
"default": 18
},
"allow_selfie_upload": {
"type": "boolean",
"default": false,
"description": "Let the person send a selfie from their photos instead of taking it with the camera. Camera only by default. Live sessions always take the selfie with the camera, through the liveness check."
},
"show_result": {
"type": "boolean",
"default": false,
"description": "Muestra a la persona el resultado en el enlace justo después de enviarlo: aprobada, rechazada o en revisión, nunca el motivo. Apagado por defecto, así que tú le avisas."
},
"expires_in": {
"type": "integer",
"minimum": 3600,
"maximum": 604800,
"default": 86400,
"description": "Seconds the link works for, from an hour to seven days. A day by default."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Uno de los proyectos de tu equipo, para agrupar sesiones. La sesión usa la configuración de Verify del proyecto donde tenga la suya."
},
"options": {
"$ref": "#/components/schemas/CreateVerifySessionOptions"
},
"expected": {
"$ref": "#/components/schemas/VerifyExpectedData"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string",
"maxLength": 500
},
"description": "Up to 20 keys of your own, returned as given."
}
}
},
"CreateVerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"minItems": 1,
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z]{3}$",
"example": "MEX"
},
"maxItems": 250,
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país."
},
"max_attempts": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página, de 1 a 5."
}
},
"additionalProperties": false
},
"rules": {
"$ref": "#/components/schemas/VerifyRuleOverrides"
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Días después de que termina la sesión en que se borran sus fotos, de 1 a 365."
}
},
"additionalProperties": false,
"description": "Lo que esta sesión debe hacer distinto de la configuración de Verify de su proyecto, o de tu equipo. Omite una clave para conservar la configuración."
},
"VerifyRuleOverrides": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"additionalProperties": false
},
"VerifyExpectedData": {
"type": "object",
"properties": {
"first_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"last_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"date_of_birth": {
"type": "string",
"format": "date",
"example": "1992-04-15"
}
},
"additionalProperties": false,
"description": "Lo que esperas que diga la identificación. Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres. Solo en el nivel `identity`. El resultado está en `checks.data_match`; de forma predeterminada, una diferencia envía la sesión a revisión."
},
"VerifySessionList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VerifySession"
}
},
"has_more": {
"type": "boolean"
}
},
"required": [
"object",
"data",
"has_more"
]
}
},
"parameters": {}
},
"paths": {
"/v1/verify/sessions": {
"post": {
"operationId": "createVerifySession",
"tags": [
"Verify"
],
"summary": "Crear una sesión",
"description": "Starts a verification and returns the `url` to send the person to. A live key sets the tier's price aside, which is kept only if the session reaches a decision.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateVerifySession"
}
}
}
},
"responses": {
"201": {
"description": "La nueva sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"400": {
"description": "Something in the request isn't valid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "Not enough credits.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Live keys need Upsky AI Pro.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "Las verificaciones reales no están conectadas en este momento, o no se pudieron leer los precios.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"operationId": "listVerifySessions",
"tags": [
"Verify"
],
"summary": "List sessions",
"description": "Your team's sessions in the key's mode, newest first. Pass the last `id` as `starting_after` for the next page.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": false,
"name": "starting_after",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
]
},
"required": false,
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "The sessions.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySessionList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}": {
"get": {
"operationId": "getVerifySession",
"tags": [
"Verify"
],
"summary": "Obtener una sesión",
"description": "Returns a session, with what was read from the ID once it's decided.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/cancel": {
"post": {
"operationId": "cancelVerifySession",
"tags": [
"Verify"
],
"summary": "Cancel a session",
"description": "Stops a session the person hasn't submitted. Its link stops working and nothing is charged.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "The canceled session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "The session was already submitted or ended.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/redact": {
"post": {
"operationId": "redactVerifySession",
"tags": [
"Verify"
],
"summary": "Redactar una sesión",
"description": "Borra de inmediato todo sobre la persona de una sesión que ya terminó: las fotos, lo que se leyó de la identificación, los datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los motivos y las revisiones se quedan como tu registro. No se puede deshacer. Redactar una sesión dos veces no cambia nada.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión redactada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "La sesión no ha terminado. Cancélala primero, o espera su resultado.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Redactar una sesión (/es/verify/reference/redactVerifySession)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Verify",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"VerifySession": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"object": {
"type": "string",
"enum": [
"verify.session"
]
},
"livemode": {
"type": "boolean"
},
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
]
},
"status": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
],
"description": "`pending` until the person opens the link, `in_progress` while they take photos, `processing` once submitted, then `approved`, `rejected` or `needs_review`. A link that's never finished ends `expired` or `canceled`, and isn't charged."
},
"url": {
"type": [
"string",
"null"
],
"description": "The link to send the person to. Null once it can't be used anymore.",
"example": "https://verify.upsky.dev/s/9Qm2..."
},
"reference": {
"type": [
"string",
"null"
]
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"project_id": {
"type": [
"string",
"null"
],
"format": "uuid"
},
"return_url": {
"type": [
"string",
"null"
]
},
"minimum_age": {
"type": "integer"
},
"allow_selfie_upload": {
"type": "boolean",
"description": "Whether the person may send a selfie from their photos. When false, it has to be taken with the camera on the page."
},
"show_result": {
"type": "boolean",
"description": "Si el enlace muestra a la persona el resultado una vez decidido."
},
"thresholds": {
"type": "object",
"properties": {
"preset": {
"type": "string",
"enum": [
"relaxed",
"balanced",
"strict",
"custom"
]
},
"liveness_pass": {
"type": "number"
},
"liveness_review": {
"type": "number"
},
"face_match_pass": {
"type": "number"
},
"face_match_review": {
"type": "number"
},
"age_buffer_years": {
"type": "integer"
}
},
"required": [
"preset",
"liveness_pass",
"liveness_review",
"face_match_pass",
"face_match_review",
"age_buffer_years"
],
"description": "Qué tan segura debía estar cada revisión, copiado al crear la sesión de la configuración de Verify de su proyecto, o de la de tu equipo si el proyecto no tiene. Una puntuación igual o mayor que la marca de aprobación pasa; desde la marca de revisión hasta la de aprobación va a revisión; por debajo de la marca de revisión se rechaza."
},
"options": {
"$ref": "#/components/schemas/VerifySessionOptions"
},
"reasons": {
"type": "array",
"items": {
"type": "string",
"enum": [
"liveness_failed",
"liveness_inconclusive",
"face_not_found",
"age_under_minimum",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_mismatch",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch",
"processing_error",
"reviewed"
]
},
"description": "Why the session ended as it did.",
"example": [
"face_mismatch"
]
},
"checks": {
"$ref": "#/components/schemas/VerifyChecks"
},
"document_type": {
"type": [
"string",
"null"
],
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card",
null
]
},
"verified_outputs": {
"$ref": "#/components/schemas/VerifiedOutputs"
},
"expires_at": {
"type": "string"
},
"created_at": {
"type": "string"
},
"started_at": {
"type": [
"string",
"null"
]
},
"submitted_at": {
"type": [
"string",
"null"
]
},
"decided_at": {
"type": [
"string",
"null"
]
},
"reviewed_at": {
"type": [
"string",
"null"
]
},
"redacted_at": {
"type": [
"string",
"null"
],
"description": "Cuándo se borraron los datos de la persona de la sesión con el endpoint de redacción."
}
},
"required": [
"id",
"object",
"livemode",
"tier",
"status",
"url",
"reference",
"metadata",
"project_id",
"return_url",
"minimum_age",
"allow_selfie_upload",
"show_result",
"thresholds",
"options",
"reasons",
"checks",
"document_type",
"verified_outputs",
"expires_at",
"created_at",
"started_at",
"submitted_at",
"decided_at",
"reviewed_at",
"redacted_at"
]
},
"VerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string"
},
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país.",
"example": [
"MEX",
"USA"
]
},
"max_attempts": {
"type": "integer",
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página. Una lectura que encuentra una foto borrosa, otro tipo de identificación, una vencida o un país que no aceptas le pide intentarlo de nuevo, hasta que se acaban."
}
},
"required": [
"allowed_types",
"allowed_countries",
"max_attempts"
]
},
"rules": {
"$ref": "#/components/schemas/VerifyRules"
},
"retention_days": {
"type": "integer",
"description": "Días después de que termina la sesión en que se borran sus fotos. Una sesión que espera revisión las conserva al menos 7 días."
}
},
"required": [
"documents",
"rules",
"retention_days"
],
"description": "Con qué opciones corre la sesión, copiadas al crearla de la configuración de Verify de su proyecto, o de tu equipo, más lo que hayas indicado en la solicitud."
},
"VerifyRules": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"required": [
"liveness_inconclusive",
"age_inconclusive",
"document_unreadable",
"document_expired",
"document_checksum_failed",
"document_type_mismatch",
"document_country_not_accepted",
"date_of_birth_missing",
"face_match_inconclusive",
"age_estimate_mismatch",
"name_mismatch",
"date_of_birth_mismatch"
],
"description": "A qué lleva cada problema: rechazar la sesión (`reject`), enviarla a revisión (`review`) o ignorarlo (`ignore`). Los problemas que no están aquí siempre rechazan: una prueba de vida fallida, que no haya rostro, un rostro que no es el de la identificación, o una edad menor a la mínima."
},
"VerifyChecks": {
"type": "object",
"properties": {
"liveness": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"confidence": {
"type": [
"number",
"null"
],
"description": "AWS Face Liveness confidence, 0 to 100."
},
"feedback": {
"type": "array",
"items": {
"type": "string",
"enum": [
"face_not_visible",
"face_obstructed",
"face_not_aligned",
"eyes_closed",
"low_lighting",
"high_lighting",
"low_video_quality"
]
},
"description": "Qué bajó la puntuación de la prueba de vida, como `high_lighting`. Vacío si nada la bajó."
}
},
"required": [
"status",
"confidence",
"feedback"
]
},
"age": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"minimum_age": {
"type": "integer"
},
"estimated_low": {
"type": [
"number",
"null"
]
},
"estimated_high": {
"type": [
"number",
"null"
]
},
"estimated_age": {
"type": [
"number",
"null"
],
"description": "La edad que aparenta la persona: el punto medio del rango estimado. En el nivel `age` pasa desde `minimum_age` más tu margen de edad."
},
"age_from_document": {
"type": [
"integer",
"null"
]
}
},
"required": [
"status",
"minimum_age",
"estimated_low",
"estimated_high",
"estimated_age",
"age_from_document"
]
},
"document": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"expired": {
"type": [
"boolean",
"null"
]
},
"checksums_valid": {
"type": [
"boolean",
"null"
]
},
"type_matches": {
"type": [
"boolean",
"null"
],
"description": "Si la identificación es del tipo que la persona eligió. Null cuando la lectura no pudo saberlo."
},
"country_accepted": {
"type": [
"boolean",
"null"
],
"description": "Si el país de la identificación es uno que la sesión acepta. Null cuando se acepta cualquiera, o no se pudo leer el país."
}
},
"required": [
"status",
"document_type",
"expired",
"checksums_valid",
"type_matches",
"country_accepted"
]
},
"face_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"similarity": {
"type": [
"number",
"null"
],
"description": "How alike the selfie and the ID's photo are, 0 to 100."
}
},
"required": [
"status",
"similarity"
]
},
"data_match": {
"type": "object",
"properties": {
"status": {
"type": "string",
"enum": [
"passed",
"failed",
"inconclusive"
]
},
"first_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"last_name": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"enum": [
"match",
"mismatch",
"not_read",
null
]
}
},
"required": [
"status",
"first_name",
"last_name",
"date_of_birth"
],
"description": "Cómo se comparó la identificación con los datos de `expected` que enviaste. Solo en sesiones creadas con ellos. Un campo es null cuando no esperabas nada para él, y `not_read` cuando la identificación no dio nada que comparar."
}
},
"description": "What each check found. Empty until the session is decided."
},
"VerifiedOutputs": {
"type": [
"object",
"null"
],
"properties": {
"first_name": {
"type": [
"string",
"null"
]
},
"last_name": {
"type": [
"string",
"null"
]
},
"date_of_birth": {
"type": [
"string",
"null"
],
"example": "1992-04-15"
},
"document_type": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"document_number": {
"type": [
"string",
"null"
]
},
"issuing_country": {
"type": [
"string",
"null"
],
"example": "MEX"
},
"expires_on": {
"type": [
"string",
"null"
],
"example": "2031-12-31"
}
},
"required": [
"first_name",
"last_name",
"date_of_birth",
"document_type",
"document_number",
"issuing_country",
"expires_on"
],
"description": "What was read from the ID. Identity tier and above, once decided. Only returned when you read one session, never in lists or webhooks."
},
"CreateVerifySession": {
"type": "object",
"properties": {
"tier": {
"type": "string",
"enum": [
"age",
"identity",
"identity_plus"
],
"default": "identity",
"description": "`age`: selfie con prueba de vida y una estimación de edad. `identity`: agrega una identificación, por ambos lados, y la comparación de rostro. `identity_plus` aún no está disponible."
},
"reference": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Your own ID for this person. Only your team sees it.",
"example": "user_123"
},
"return_url": {
"type": "string",
"maxLength": 2000,
"description": "Where to send the person once they're done: a web page, or your app through its own scheme (`myapp://verified`) to bring them back from an in-app browser. The page goes there by itself a few seconds after they submit, or after the result when `show_result` is on, and also shows a button.",
"example": "https://example.com/verified"
},
"minimum_age": {
"type": "integer",
"minimum": 13,
"maximum": 25,
"default": 18
},
"allow_selfie_upload": {
"type": "boolean",
"default": false,
"description": "Let the person send a selfie from their photos instead of taking it with the camera. Camera only by default. Live sessions always take the selfie with the camera, through the liveness check."
},
"show_result": {
"type": "boolean",
"default": false,
"description": "Muestra a la persona el resultado en el enlace justo después de enviarlo: aprobada, rechazada o en revisión, nunca el motivo. Apagado por defecto, así que tú le avisas."
},
"expires_in": {
"type": "integer",
"minimum": 3600,
"maximum": 604800,
"default": 86400,
"description": "Seconds the link works for, from an hour to seven days. A day by default."
},
"project_id": {
"type": "string",
"format": "uuid",
"description": "Uno de los proyectos de tu equipo, para agrupar sesiones. La sesión usa la configuración de Verify del proyecto donde tenga la suya."
},
"options": {
"$ref": "#/components/schemas/CreateVerifySessionOptions"
},
"expected": {
"$ref": "#/components/schemas/VerifyExpectedData"
},
"metadata": {
"type": "object",
"additionalProperties": {
"type": "string",
"maxLength": 500
},
"description": "Up to 20 keys of your own, returned as given."
}
}
},
"CreateVerifySessionOptions": {
"type": "object",
"properties": {
"documents": {
"type": "object",
"properties": {
"allowed_types": {
"type": "array",
"items": {
"type": "string",
"enum": [
"mx_ine",
"passport",
"us_drivers_license",
"id_card"
]
},
"minItems": 1,
"description": "Las identificaciones que la persona puede usar."
},
"allowed_countries": {
"type": "array",
"items": {
"type": "string",
"pattern": "^[A-Za-z]{3}$",
"example": "MEX"
},
"maxItems": 250,
"description": "Países emisores aceptados, como códigos ISO 3166-1 alfa-3. Vacío significa cualquier país."
},
"max_attempts": {
"type": "integer",
"minimum": 1,
"maximum": 5,
"description": "Cuántas veces se leen las fotos de la identificación mientras la persona está en la página, de 1 a 5."
}
},
"additionalProperties": false
},
"rules": {
"$ref": "#/components/schemas/VerifyRuleOverrides"
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Días después de que termina la sesión en que se borran sus fotos, de 1 a 365."
}
},
"additionalProperties": false,
"description": "Lo que esta sesión debe hacer distinto de la configuración de Verify de su proyecto, o de tu equipo. Omite una clave para conservar la configuración."
},
"VerifyRuleOverrides": {
"type": "object",
"properties": {
"liveness_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_unreadable": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_expired": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `reject` salvo que lo cambies."
},
"document_checksum_failed": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"document_type_mismatch": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"document_country_not_accepted": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `reject` salvo que lo cambies."
},
"date_of_birth_missing": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"face_match_inconclusive": {
"type": "string",
"enum": [
"reject",
"review"
],
"description": "Uno de `reject`, `review`. `review` salvo que lo cambies."
},
"age_estimate_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"name_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
},
"date_of_birth_mismatch": {
"type": "string",
"enum": [
"reject",
"review",
"ignore"
],
"description": "Uno de `reject`, `review`, `ignore`. `review` salvo que lo cambies."
}
},
"additionalProperties": false
},
"VerifyExpectedData": {
"type": "object",
"properties": {
"first_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"last_name": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"date_of_birth": {
"type": "string",
"format": "date",
"example": "1992-04-15"
}
},
"additionalProperties": false,
"description": "Lo que esperas que diga la identificación. Los nombres coinciden sin importar acentos, mayúsculas o segundos nombres. Solo en el nivel `identity`. El resultado está en `checks.data_match`; de forma predeterminada, una diferencia envía la sesión a revisión."
},
"VerifySessionList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/VerifySession"
}
},
"has_more": {
"type": "boolean"
}
},
"required": [
"object",
"data",
"has_more"
]
}
},
"parameters": {}
},
"paths": {
"/v1/verify/sessions": {
"post": {
"operationId": "createVerifySession",
"tags": [
"Verify"
],
"summary": "Crear una sesión",
"description": "Starts a verification and returns the `url` to send the person to. A live key sets the tier's price aside, which is kept only if the session reaches a decision.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateVerifySession"
}
}
}
},
"responses": {
"201": {
"description": "La nueva sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"400": {
"description": "Something in the request isn't valid.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "Not enough credits.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Live keys need Upsky AI Pro.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "Las verificaciones reales no están conectadas en este momento, o no se pudieron leer los precios.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"get": {
"operationId": "listVerifySessions",
"tags": [
"Verify"
],
"summary": "List sessions",
"description": "Your team's sessions in the key's mode, newest first. Pass the last `id` as `starting_after` for the next page.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 20
},
"required": false,
"name": "limit",
"in": "query"
},
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": false,
"name": "starting_after",
"in": "query"
},
{
"schema": {
"type": "string",
"enum": [
"pending",
"in_progress",
"processing",
"approved",
"rejected",
"needs_review",
"expired",
"canceled"
]
},
"required": false,
"name": "status",
"in": "query"
}
],
"responses": {
"200": {
"description": "The sessions.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySessionList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}": {
"get": {
"operationId": "getVerifySession",
"tags": [
"Verify"
],
"summary": "Obtener una sesión",
"description": "Returns a session, with what was read from the ID once it's decided.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/cancel": {
"post": {
"operationId": "cancelVerifySession",
"tags": [
"Verify"
],
"summary": "Cancel a session",
"description": "Stops a session the person hasn't submitted. Its link stops working and nothing is charged.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "The canceled session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "The session was already submitted or ended.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/verify/sessions/{id}/redact": {
"post": {
"operationId": "redactVerifySession",
"tags": [
"Verify"
],
"summary": "Redactar una sesión",
"description": "Borra de inmediato todo sobre la persona de una sesión que ya terminó: las fotos, lo que se leyó de la identificación, los datos de `expected`, y tu `reference`, `metadata` y `return_url`. El estado, los motivos y las revisiones se quedan como tu registro. No se puede deshacer. Redactar una sesión dos veces no cambia nada.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"format": "uuid"
},
"required": true,
"name": "id",
"in": "path"
}
],
"responses": {
"200": {
"description": "La sesión redactada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifySession"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No such session.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "La sesión no ha terminado. Cancélala primero, o espera su resultado.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Listar modelos (/es/ai/reference/listModels)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky AI",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"ModelList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Model"
}
}
},
"required": [
"object",
"data"
]
},
"Model": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Envíalo como `model`. Siempre tiene la forma `provider/model-name`.",
"example": "anthropic/claude-sonnet-5"
},
"object": {
"type": "string",
"enum": [
"model"
]
},
"type": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Qué endpoint lo usa: respuestas de chat, generación de imágenes o voz.",
"example": "text"
},
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"owned_by": {
"type": "string",
"example": "anthropic"
},
"name": {
"type": "string",
"example": "Claude Sonnet 5"
},
"context_window": {
"type": [
"integer",
"null"
],
"description": "El máximo de tokens que lee el modelo, sumando la instrucción y la respuesta. Nulo para los modelos de imagen y de voz."
},
"max_output_tokens": {
"type": [
"integer",
"null"
],
"description": "La respuesta más larga que escribe el modelo, en tokens. Nulo para los modelos de imagen y de voz."
},
"pricing": {
"$ref": "#/components/schemas/ModelPricing"
}
},
"required": [
"id",
"object",
"type",
"created",
"owned_by",
"name",
"context_window",
"max_output_tokens",
"pricing"
]
},
"ModelPricing": {
"type": "object",
"properties": {
"input": {
"type": "number",
"description": "Por millón de tokens de la instrucción.",
"example": 3.3
},
"output": {
"type": "number",
"description": "Por millón de tokens que escribe el modelo.",
"example": 16.5
},
"cached_input": {
"type": [
"number",
"null"
],
"description": "Por millón de tokens de la instrucción leídos de la caché.",
"example": 0.33
},
"image": {
"type": "number",
"description": "Por imagen, en el tamaño predeterminado del modelo.",
"example": 0.0528
},
"image_sizes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string"
},
"image": {
"type": "number"
}
},
"required": [
"size",
"image"
]
},
"description": "Por imagen en cada tamaño que indica el modelo, primero el más pequeño. Un tamaño intermedio cuesta lo mismo que el siguiente más grande."
},
"characters": {
"type": "number",
"description": "Por millón de caracteres de entrada.",
"example": 16.5
}
},
"description": "Dólares estadounidenses, con los campos que corresponden al tipo de modelo. Los modelos de texto, y los de imagen sin precio por imagen, se cobran por millón de tokens; algunos cobran más por instrucciones muy largas. Los modelos de imagen se cobran por imagen y los de voz por carácter."
},
"ChatCompletion": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"chat.completion"
]
},
"created": {
"type": "integer"
},
"model": {
"type": "string"
},
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer"
},
"message": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"content": {
"type": [
"string",
"null"
]
}
},
"required": [
"role",
"content"
],
"additionalProperties": {}
},
"finish_reason": {
"type": [
"string",
"null"
]
}
},
"required": [
"index",
"message",
"finish_reason"
],
"additionalProperties": {}
}
},
"usage": {
"$ref": "#/components/schemas/Usage"
}
},
"required": [
"id",
"object",
"created",
"model",
"choices",
"usage"
],
"additionalProperties": {}
},
"Usage": {
"type": "object",
"properties": {
"prompt_tokens": {
"type": "integer"
},
"completion_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.00042
}
},
"required": [
"prompt_tokens",
"completion_tokens",
"cost"
],
"additionalProperties": {}
},
"ChatCompletionRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de `GET /v1/ai/models`.",
"example": "anthropic/claude-sonnet-5"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChatMessage"
},
"minItems": 1
},
"stream": {
"type": "boolean",
"description": "Envía la respuesta como eventos enviados por el servidor mientras se escribe. El último evento antes de `[DONE]` indica el uso y el costo."
},
"max_completion_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "La respuesta más larga permitida, en tokens. Sin este campo, un equipo sin recarga automática puede recibir un límite menor que sus créditos cubran."
},
"max_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "Nombre anterior de `max_completion_tokens`."
},
"temperature": {
"type": "number"
},
"tools": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"function"
]
}
},
"required": [
"type"
],
"additionalProperties": {}
},
"description": "Herramientas de tipo función. Otros tipos de herramienta se rechazan."
},
"n": {
"type": "integer",
"description": "Solo se admite 1."
}
},
"required": [
"model",
"messages"
],
"additionalProperties": {},
"description": "El formato Chat Completions de OpenAI. Los campos que no aparecen aquí se envían al modelo tal cual, excepto `providerOptions` distinto de `gateway.order`, `gateway.only` y `gateway.sort`, y `user`, que se descartan."
},
"ChatMessage": {
"type": "object",
"properties": {
"role": {
"type": "string",
"minLength": 1,
"example": "user"
},
"content": {
"description": "Texto, o un arreglo de partes de texto, imagen y archivo.",
"example": "Hello"
}
},
"required": [
"role"
],
"additionalProperties": {}
},
"ImageGeneration": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"b64_json": {
"type": "string",
"description": "La imagen, codificada en base64. Normalmente un PNG."
}
},
"required": [
"b64_json"
]
}
},
"usage": {
"$ref": "#/components/schemas/ImageUsage"
}
},
"required": [
"created",
"data",
"usage"
]
},
"ImageUsage": {
"type": "object",
"properties": {
"images": {
"type": "integer",
"description": "Imágenes creadas."
},
"input_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"output_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.0528
}
},
"required": [
"images",
"cost"
]
},
"ImageGenerationRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de imagen de `GET /v1/ai/models?type=image`.",
"example": "bfl/flux-2-pro"
},
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 32000,
"description": "Lo que quieres que dibuje.",
"example": "A lighthouse on a cliff at dawn, watercolor"
},
"n": {
"type": "integer",
"minimum": 1,
"maximum": 4,
"description": "Cuántas imágenes crear, hasta 4. El valor predeterminado es 1. Se cobra cada una."
},
"size": {
"type": "string",
"pattern": "^\\d{2,5}x\\d{2,5}$",
"description": "Ancho y alto en píxeles, como `1024x1024`. Los tamaños que admite cada modelo varían.",
"example": "1024x1024"
},
"aspect_ratio": {
"type": "string",
"pattern": "^\\d{1,3}:\\d{1,3}$",
"description": "Proporción entre ancho y alto, como `16:9`, para los modelos que usan una proporción en lugar de un tamaño.",
"example": "16:9"
},
"seed": {
"type": "integer",
"description": "Hace que los resultados se puedan repetir, en los modelos que lo admiten."
},
"response_format": {
"type": "string",
"enum": [
"b64_json"
],
"description": "Solo `b64_json`: las imágenes se devuelven en base64, nunca como enlaces."
}
},
"required": [
"model",
"prompt"
],
"additionalProperties": {},
"description": "El formato Images de OpenAI. Los demás campos, como `quality` o `style`, se ignoran."
},
"SpeechRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de voz de `GET /v1/ai/models?type=speech`.",
"example": "openai/tts-1"
},
"input": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "El texto que se leerá, hasta 5000 caracteres. Se cobra cada carácter.",
"example": "Your order shipped this morning."
},
"voice": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "La voz que se usará. Cada proveedor tiene las suyas, como `alloy` en OpenAI. Sin este campo, la predeterminada del modelo.",
"example": "alloy"
},
"response_format": {
"type": "string",
"enum": [
"mp3",
"opus",
"aac",
"flac",
"wav",
"pcm"
],
"description": "El formato del audio. Los modelos que no pueden generarlo responden en el suyo, así que lee el encabezado `Content-Type`."
},
"instructions": {
"type": "string",
"maxLength": 4096,
"description": "Cómo debe hablar, por ejemplo el tono o el ritmo, en los modelos que lo admiten."
},
"speed": {
"type": "number",
"minimum": 0.25,
"maximum": 4,
"description": "De 0.25 a 4. El valor predeterminado es 1, en los modelos que lo admiten."
},
"language": {
"type": "string",
"minLength": 2,
"maxLength": 35,
"description": "El idioma del texto, como `en`, cuando importa."
}
},
"required": [
"model",
"input"
],
"additionalProperties": {},
"description": "El formato de voz de OpenAI. Los demás campos se ignoran."
}
},
"parameters": {}
},
"paths": {
"/v1/ai/models": {
"get": {
"operationId": "listModels",
"tags": [
"AI"
],
"summary": "Listar modelos",
"description": "Devuelve todos los modelos que puedes usar, primero los más recientes, con su precio.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Solo los modelos de este tipo."
},
"required": false,
"description": "Solo los modelos de este tipo.",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "Los modelos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModelList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "No se pudo leer la lista de modelos. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/chat/completions": {
"post": {
"operationId": "createChatCompletion",
"tags": [
"AI"
],
"summary": "Crear una respuesta de chat",
"description": "Envía mensajes a un modelo y devuelve su respuesta, en el formato Chat Completions de OpenAI. Con una clave de prueba la respuesta es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletionRequest"
}
}
}
},
"responses": {
"200": {
"description": "La respuesta del modelo o, con `stream: true`, un flujo de eventos `chat.completion.chunk`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletion"
}
},
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/images/generations": {
"post": {
"operationId": "createImage",
"tags": [
"AI"
],
"summary": "Crear una imagen",
"description": "Crea imágenes a partir de una instrucción, en el formato Images de OpenAI. Con una clave de prueba la imagen es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGenerationRequest"
}
}
}
},
"responses": {
"200": {
"description": "Las imágenes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGeneration"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/audio/speech": {
"post": {
"operationId": "createSpeech",
"tags": [
"AI"
],
"summary": "Crear voz",
"description": "Lee un texto en voz alta y devuelve el archivo de audio. Con una clave de prueba el audio es un segundo de silencio, en WAV, y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpeechRequest"
}
}
}
},
"responses": {
"200": {
"description": "El audio. `Upsky-Cost` indica lo que se cobró, en dólares estadounidenses, y `Upsky-Characters` cuántos caracteres se cobraron.",
"headers": {
"Upsky-Cost": {
"schema": {
"type": "string",
"example": "0.0165"
},
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses."
},
"Upsky-Characters": {
"schema": {
"type": "string",
"example": "1000"
},
"description": "Caracteres cobrados."
}
},
"content": {
"audio/mpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"audio/wav": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Crear una respuesta de chat (/es/ai/reference/createChatCompletion)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky AI",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"ModelList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Model"
}
}
},
"required": [
"object",
"data"
]
},
"Model": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Envíalo como `model`. Siempre tiene la forma `provider/model-name`.",
"example": "anthropic/claude-sonnet-5"
},
"object": {
"type": "string",
"enum": [
"model"
]
},
"type": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Qué endpoint lo usa: respuestas de chat, generación de imágenes o voz.",
"example": "text"
},
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"owned_by": {
"type": "string",
"example": "anthropic"
},
"name": {
"type": "string",
"example": "Claude Sonnet 5"
},
"context_window": {
"type": [
"integer",
"null"
],
"description": "El máximo de tokens que lee el modelo, sumando la instrucción y la respuesta. Nulo para los modelos de imagen y de voz."
},
"max_output_tokens": {
"type": [
"integer",
"null"
],
"description": "La respuesta más larga que escribe el modelo, en tokens. Nulo para los modelos de imagen y de voz."
},
"pricing": {
"$ref": "#/components/schemas/ModelPricing"
}
},
"required": [
"id",
"object",
"type",
"created",
"owned_by",
"name",
"context_window",
"max_output_tokens",
"pricing"
]
},
"ModelPricing": {
"type": "object",
"properties": {
"input": {
"type": "number",
"description": "Por millón de tokens de la instrucción.",
"example": 3.3
},
"output": {
"type": "number",
"description": "Por millón de tokens que escribe el modelo.",
"example": 16.5
},
"cached_input": {
"type": [
"number",
"null"
],
"description": "Por millón de tokens de la instrucción leídos de la caché.",
"example": 0.33
},
"image": {
"type": "number",
"description": "Por imagen, en el tamaño predeterminado del modelo.",
"example": 0.0528
},
"image_sizes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string"
},
"image": {
"type": "number"
}
},
"required": [
"size",
"image"
]
},
"description": "Por imagen en cada tamaño que indica el modelo, primero el más pequeño. Un tamaño intermedio cuesta lo mismo que el siguiente más grande."
},
"characters": {
"type": "number",
"description": "Por millón de caracteres de entrada.",
"example": 16.5
}
},
"description": "Dólares estadounidenses, con los campos que corresponden al tipo de modelo. Los modelos de texto, y los de imagen sin precio por imagen, se cobran por millón de tokens; algunos cobran más por instrucciones muy largas. Los modelos de imagen se cobran por imagen y los de voz por carácter."
},
"ChatCompletion": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"chat.completion"
]
},
"created": {
"type": "integer"
},
"model": {
"type": "string"
},
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer"
},
"message": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"content": {
"type": [
"string",
"null"
]
}
},
"required": [
"role",
"content"
],
"additionalProperties": {}
},
"finish_reason": {
"type": [
"string",
"null"
]
}
},
"required": [
"index",
"message",
"finish_reason"
],
"additionalProperties": {}
}
},
"usage": {
"$ref": "#/components/schemas/Usage"
}
},
"required": [
"id",
"object",
"created",
"model",
"choices",
"usage"
],
"additionalProperties": {}
},
"Usage": {
"type": "object",
"properties": {
"prompt_tokens": {
"type": "integer"
},
"completion_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.00042
}
},
"required": [
"prompt_tokens",
"completion_tokens",
"cost"
],
"additionalProperties": {}
},
"ChatCompletionRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de `GET /v1/ai/models`.",
"example": "anthropic/claude-sonnet-5"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChatMessage"
},
"minItems": 1
},
"stream": {
"type": "boolean",
"description": "Envía la respuesta como eventos enviados por el servidor mientras se escribe. El último evento antes de `[DONE]` indica el uso y el costo."
},
"max_completion_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "La respuesta más larga permitida, en tokens. Sin este campo, un equipo sin recarga automática puede recibir un límite menor que sus créditos cubran."
},
"max_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "Nombre anterior de `max_completion_tokens`."
},
"temperature": {
"type": "number"
},
"tools": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"function"
]
}
},
"required": [
"type"
],
"additionalProperties": {}
},
"description": "Herramientas de tipo función. Otros tipos de herramienta se rechazan."
},
"n": {
"type": "integer",
"description": "Solo se admite 1."
}
},
"required": [
"model",
"messages"
],
"additionalProperties": {},
"description": "El formato Chat Completions de OpenAI. Los campos que no aparecen aquí se envían al modelo tal cual, excepto `providerOptions` distinto de `gateway.order`, `gateway.only` y `gateway.sort`, y `user`, que se descartan."
},
"ChatMessage": {
"type": "object",
"properties": {
"role": {
"type": "string",
"minLength": 1,
"example": "user"
},
"content": {
"description": "Texto, o un arreglo de partes de texto, imagen y archivo.",
"example": "Hello"
}
},
"required": [
"role"
],
"additionalProperties": {}
},
"ImageGeneration": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"b64_json": {
"type": "string",
"description": "La imagen, codificada en base64. Normalmente un PNG."
}
},
"required": [
"b64_json"
]
}
},
"usage": {
"$ref": "#/components/schemas/ImageUsage"
}
},
"required": [
"created",
"data",
"usage"
]
},
"ImageUsage": {
"type": "object",
"properties": {
"images": {
"type": "integer",
"description": "Imágenes creadas."
},
"input_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"output_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.0528
}
},
"required": [
"images",
"cost"
]
},
"ImageGenerationRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de imagen de `GET /v1/ai/models?type=image`.",
"example": "bfl/flux-2-pro"
},
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 32000,
"description": "Lo que quieres que dibuje.",
"example": "A lighthouse on a cliff at dawn, watercolor"
},
"n": {
"type": "integer",
"minimum": 1,
"maximum": 4,
"description": "Cuántas imágenes crear, hasta 4. El valor predeterminado es 1. Se cobra cada una."
},
"size": {
"type": "string",
"pattern": "^\\d{2,5}x\\d{2,5}$",
"description": "Ancho y alto en píxeles, como `1024x1024`. Los tamaños que admite cada modelo varían.",
"example": "1024x1024"
},
"aspect_ratio": {
"type": "string",
"pattern": "^\\d{1,3}:\\d{1,3}$",
"description": "Proporción entre ancho y alto, como `16:9`, para los modelos que usan una proporción en lugar de un tamaño.",
"example": "16:9"
},
"seed": {
"type": "integer",
"description": "Hace que los resultados se puedan repetir, en los modelos que lo admiten."
},
"response_format": {
"type": "string",
"enum": [
"b64_json"
],
"description": "Solo `b64_json`: las imágenes se devuelven en base64, nunca como enlaces."
}
},
"required": [
"model",
"prompt"
],
"additionalProperties": {},
"description": "El formato Images de OpenAI. Los demás campos, como `quality` o `style`, se ignoran."
},
"SpeechRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de voz de `GET /v1/ai/models?type=speech`.",
"example": "openai/tts-1"
},
"input": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "El texto que se leerá, hasta 5000 caracteres. Se cobra cada carácter.",
"example": "Your order shipped this morning."
},
"voice": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "La voz que se usará. Cada proveedor tiene las suyas, como `alloy` en OpenAI. Sin este campo, la predeterminada del modelo.",
"example": "alloy"
},
"response_format": {
"type": "string",
"enum": [
"mp3",
"opus",
"aac",
"flac",
"wav",
"pcm"
],
"description": "El formato del audio. Los modelos que no pueden generarlo responden en el suyo, así que lee el encabezado `Content-Type`."
},
"instructions": {
"type": "string",
"maxLength": 4096,
"description": "Cómo debe hablar, por ejemplo el tono o el ritmo, en los modelos que lo admiten."
},
"speed": {
"type": "number",
"minimum": 0.25,
"maximum": 4,
"description": "De 0.25 a 4. El valor predeterminado es 1, en los modelos que lo admiten."
},
"language": {
"type": "string",
"minLength": 2,
"maxLength": 35,
"description": "El idioma del texto, como `en`, cuando importa."
}
},
"required": [
"model",
"input"
],
"additionalProperties": {},
"description": "El formato de voz de OpenAI. Los demás campos se ignoran."
}
},
"parameters": {}
},
"paths": {
"/v1/ai/models": {
"get": {
"operationId": "listModels",
"tags": [
"AI"
],
"summary": "Listar modelos",
"description": "Devuelve todos los modelos que puedes usar, primero los más recientes, con su precio.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Solo los modelos de este tipo."
},
"required": false,
"description": "Solo los modelos de este tipo.",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "Los modelos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModelList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "No se pudo leer la lista de modelos. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/chat/completions": {
"post": {
"operationId": "createChatCompletion",
"tags": [
"AI"
],
"summary": "Crear una respuesta de chat",
"description": "Envía mensajes a un modelo y devuelve su respuesta, en el formato Chat Completions de OpenAI. Con una clave de prueba la respuesta es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletionRequest"
}
}
}
},
"responses": {
"200": {
"description": "La respuesta del modelo o, con `stream: true`, un flujo de eventos `chat.completion.chunk`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletion"
}
},
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/images/generations": {
"post": {
"operationId": "createImage",
"tags": [
"AI"
],
"summary": "Crear una imagen",
"description": "Crea imágenes a partir de una instrucción, en el formato Images de OpenAI. Con una clave de prueba la imagen es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGenerationRequest"
}
}
}
},
"responses": {
"200": {
"description": "Las imágenes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGeneration"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/audio/speech": {
"post": {
"operationId": "createSpeech",
"tags": [
"AI"
],
"summary": "Crear voz",
"description": "Lee un texto en voz alta y devuelve el archivo de audio. Con una clave de prueba el audio es un segundo de silencio, en WAV, y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpeechRequest"
}
}
}
},
"responses": {
"200": {
"description": "El audio. `Upsky-Cost` indica lo que se cobró, en dólares estadounidenses, y `Upsky-Characters` cuántos caracteres se cobraron.",
"headers": {
"Upsky-Cost": {
"schema": {
"type": "string",
"example": "0.0165"
},
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses."
},
"Upsky-Characters": {
"schema": {
"type": "string",
"example": "1000"
},
"description": "Caracteres cobrados."
}
},
"content": {
"audio/mpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"audio/wav": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Crear una imagen (/es/ai/reference/createImage)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky AI",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"ModelList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Model"
}
}
},
"required": [
"object",
"data"
]
},
"Model": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Envíalo como `model`. Siempre tiene la forma `provider/model-name`.",
"example": "anthropic/claude-sonnet-5"
},
"object": {
"type": "string",
"enum": [
"model"
]
},
"type": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Qué endpoint lo usa: respuestas de chat, generación de imágenes o voz.",
"example": "text"
},
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"owned_by": {
"type": "string",
"example": "anthropic"
},
"name": {
"type": "string",
"example": "Claude Sonnet 5"
},
"context_window": {
"type": [
"integer",
"null"
],
"description": "El máximo de tokens que lee el modelo, sumando la instrucción y la respuesta. Nulo para los modelos de imagen y de voz."
},
"max_output_tokens": {
"type": [
"integer",
"null"
],
"description": "La respuesta más larga que escribe el modelo, en tokens. Nulo para los modelos de imagen y de voz."
},
"pricing": {
"$ref": "#/components/schemas/ModelPricing"
}
},
"required": [
"id",
"object",
"type",
"created",
"owned_by",
"name",
"context_window",
"max_output_tokens",
"pricing"
]
},
"ModelPricing": {
"type": "object",
"properties": {
"input": {
"type": "number",
"description": "Por millón de tokens de la instrucción.",
"example": 3.3
},
"output": {
"type": "number",
"description": "Por millón de tokens que escribe el modelo.",
"example": 16.5
},
"cached_input": {
"type": [
"number",
"null"
],
"description": "Por millón de tokens de la instrucción leídos de la caché.",
"example": 0.33
},
"image": {
"type": "number",
"description": "Por imagen, en el tamaño predeterminado del modelo.",
"example": 0.0528
},
"image_sizes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string"
},
"image": {
"type": "number"
}
},
"required": [
"size",
"image"
]
},
"description": "Por imagen en cada tamaño que indica el modelo, primero el más pequeño. Un tamaño intermedio cuesta lo mismo que el siguiente más grande."
},
"characters": {
"type": "number",
"description": "Por millón de caracteres de entrada.",
"example": 16.5
}
},
"description": "Dólares estadounidenses, con los campos que corresponden al tipo de modelo. Los modelos de texto, y los de imagen sin precio por imagen, se cobran por millón de tokens; algunos cobran más por instrucciones muy largas. Los modelos de imagen se cobran por imagen y los de voz por carácter."
},
"ChatCompletion": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"chat.completion"
]
},
"created": {
"type": "integer"
},
"model": {
"type": "string"
},
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer"
},
"message": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"content": {
"type": [
"string",
"null"
]
}
},
"required": [
"role",
"content"
],
"additionalProperties": {}
},
"finish_reason": {
"type": [
"string",
"null"
]
}
},
"required": [
"index",
"message",
"finish_reason"
],
"additionalProperties": {}
}
},
"usage": {
"$ref": "#/components/schemas/Usage"
}
},
"required": [
"id",
"object",
"created",
"model",
"choices",
"usage"
],
"additionalProperties": {}
},
"Usage": {
"type": "object",
"properties": {
"prompt_tokens": {
"type": "integer"
},
"completion_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.00042
}
},
"required": [
"prompt_tokens",
"completion_tokens",
"cost"
],
"additionalProperties": {}
},
"ChatCompletionRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de `GET /v1/ai/models`.",
"example": "anthropic/claude-sonnet-5"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChatMessage"
},
"minItems": 1
},
"stream": {
"type": "boolean",
"description": "Envía la respuesta como eventos enviados por el servidor mientras se escribe. El último evento antes de `[DONE]` indica el uso y el costo."
},
"max_completion_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "La respuesta más larga permitida, en tokens. Sin este campo, un equipo sin recarga automática puede recibir un límite menor que sus créditos cubran."
},
"max_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "Nombre anterior de `max_completion_tokens`."
},
"temperature": {
"type": "number"
},
"tools": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"function"
]
}
},
"required": [
"type"
],
"additionalProperties": {}
},
"description": "Herramientas de tipo función. Otros tipos de herramienta se rechazan."
},
"n": {
"type": "integer",
"description": "Solo se admite 1."
}
},
"required": [
"model",
"messages"
],
"additionalProperties": {},
"description": "El formato Chat Completions de OpenAI. Los campos que no aparecen aquí se envían al modelo tal cual, excepto `providerOptions` distinto de `gateway.order`, `gateway.only` y `gateway.sort`, y `user`, que se descartan."
},
"ChatMessage": {
"type": "object",
"properties": {
"role": {
"type": "string",
"minLength": 1,
"example": "user"
},
"content": {
"description": "Texto, o un arreglo de partes de texto, imagen y archivo.",
"example": "Hello"
}
},
"required": [
"role"
],
"additionalProperties": {}
},
"ImageGeneration": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"b64_json": {
"type": "string",
"description": "La imagen, codificada en base64. Normalmente un PNG."
}
},
"required": [
"b64_json"
]
}
},
"usage": {
"$ref": "#/components/schemas/ImageUsage"
}
},
"required": [
"created",
"data",
"usage"
]
},
"ImageUsage": {
"type": "object",
"properties": {
"images": {
"type": "integer",
"description": "Imágenes creadas."
},
"input_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"output_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.0528
}
},
"required": [
"images",
"cost"
]
},
"ImageGenerationRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de imagen de `GET /v1/ai/models?type=image`.",
"example": "bfl/flux-2-pro"
},
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 32000,
"description": "Lo que quieres que dibuje.",
"example": "A lighthouse on a cliff at dawn, watercolor"
},
"n": {
"type": "integer",
"minimum": 1,
"maximum": 4,
"description": "Cuántas imágenes crear, hasta 4. El valor predeterminado es 1. Se cobra cada una."
},
"size": {
"type": "string",
"pattern": "^\\d{2,5}x\\d{2,5}$",
"description": "Ancho y alto en píxeles, como `1024x1024`. Los tamaños que admite cada modelo varían.",
"example": "1024x1024"
},
"aspect_ratio": {
"type": "string",
"pattern": "^\\d{1,3}:\\d{1,3}$",
"description": "Proporción entre ancho y alto, como `16:9`, para los modelos que usan una proporción en lugar de un tamaño.",
"example": "16:9"
},
"seed": {
"type": "integer",
"description": "Hace que los resultados se puedan repetir, en los modelos que lo admiten."
},
"response_format": {
"type": "string",
"enum": [
"b64_json"
],
"description": "Solo `b64_json`: las imágenes se devuelven en base64, nunca como enlaces."
}
},
"required": [
"model",
"prompt"
],
"additionalProperties": {},
"description": "El formato Images de OpenAI. Los demás campos, como `quality` o `style`, se ignoran."
},
"SpeechRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de voz de `GET /v1/ai/models?type=speech`.",
"example": "openai/tts-1"
},
"input": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "El texto que se leerá, hasta 5000 caracteres. Se cobra cada carácter.",
"example": "Your order shipped this morning."
},
"voice": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "La voz que se usará. Cada proveedor tiene las suyas, como `alloy` en OpenAI. Sin este campo, la predeterminada del modelo.",
"example": "alloy"
},
"response_format": {
"type": "string",
"enum": [
"mp3",
"opus",
"aac",
"flac",
"wav",
"pcm"
],
"description": "El formato del audio. Los modelos que no pueden generarlo responden en el suyo, así que lee el encabezado `Content-Type`."
},
"instructions": {
"type": "string",
"maxLength": 4096,
"description": "Cómo debe hablar, por ejemplo el tono o el ritmo, en los modelos que lo admiten."
},
"speed": {
"type": "number",
"minimum": 0.25,
"maximum": 4,
"description": "De 0.25 a 4. El valor predeterminado es 1, en los modelos que lo admiten."
},
"language": {
"type": "string",
"minLength": 2,
"maxLength": 35,
"description": "El idioma del texto, como `en`, cuando importa."
}
},
"required": [
"model",
"input"
],
"additionalProperties": {},
"description": "El formato de voz de OpenAI. Los demás campos se ignoran."
}
},
"parameters": {}
},
"paths": {
"/v1/ai/models": {
"get": {
"operationId": "listModels",
"tags": [
"AI"
],
"summary": "Listar modelos",
"description": "Devuelve todos los modelos que puedes usar, primero los más recientes, con su precio.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Solo los modelos de este tipo."
},
"required": false,
"description": "Solo los modelos de este tipo.",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "Los modelos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModelList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "No se pudo leer la lista de modelos. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/chat/completions": {
"post": {
"operationId": "createChatCompletion",
"tags": [
"AI"
],
"summary": "Crear una respuesta de chat",
"description": "Envía mensajes a un modelo y devuelve su respuesta, en el formato Chat Completions de OpenAI. Con una clave de prueba la respuesta es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletionRequest"
}
}
}
},
"responses": {
"200": {
"description": "La respuesta del modelo o, con `stream: true`, un flujo de eventos `chat.completion.chunk`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletion"
}
},
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/images/generations": {
"post": {
"operationId": "createImage",
"tags": [
"AI"
],
"summary": "Crear una imagen",
"description": "Crea imágenes a partir de una instrucción, en el formato Images de OpenAI. Con una clave de prueba la imagen es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGenerationRequest"
}
}
}
},
"responses": {
"200": {
"description": "Las imágenes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGeneration"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/audio/speech": {
"post": {
"operationId": "createSpeech",
"tags": [
"AI"
],
"summary": "Crear voz",
"description": "Lee un texto en voz alta y devuelve el archivo de audio. Con una clave de prueba el audio es un segundo de silencio, en WAV, y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpeechRequest"
}
}
}
},
"responses": {
"200": {
"description": "El audio. `Upsky-Cost` indica lo que se cobró, en dólares estadounidenses, y `Upsky-Characters` cuántos caracteres se cobraron.",
"headers": {
"Upsky-Cost": {
"schema": {
"type": "string",
"example": "0.0165"
},
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses."
},
"Upsky-Characters": {
"schema": {
"type": "string",
"example": "1000"
},
"description": "Caracteres cobrados."
}
},
"content": {
"audio/mpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"audio/wav": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Crear voz (/es/ai/reference/createSpeech)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky AI",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
},
"ModelList": {
"type": "object",
"properties": {
"object": {
"type": "string",
"enum": [
"list"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Model"
}
}
},
"required": [
"object",
"data"
]
},
"Model": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Envíalo como `model`. Siempre tiene la forma `provider/model-name`.",
"example": "anthropic/claude-sonnet-5"
},
"object": {
"type": "string",
"enum": [
"model"
]
},
"type": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Qué endpoint lo usa: respuestas de chat, generación de imágenes o voz.",
"example": "text"
},
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"owned_by": {
"type": "string",
"example": "anthropic"
},
"name": {
"type": "string",
"example": "Claude Sonnet 5"
},
"context_window": {
"type": [
"integer",
"null"
],
"description": "El máximo de tokens que lee el modelo, sumando la instrucción y la respuesta. Nulo para los modelos de imagen y de voz."
},
"max_output_tokens": {
"type": [
"integer",
"null"
],
"description": "La respuesta más larga que escribe el modelo, en tokens. Nulo para los modelos de imagen y de voz."
},
"pricing": {
"$ref": "#/components/schemas/ModelPricing"
}
},
"required": [
"id",
"object",
"type",
"created",
"owned_by",
"name",
"context_window",
"max_output_tokens",
"pricing"
]
},
"ModelPricing": {
"type": "object",
"properties": {
"input": {
"type": "number",
"description": "Por millón de tokens de la instrucción.",
"example": 3.3
},
"output": {
"type": "number",
"description": "Por millón de tokens que escribe el modelo.",
"example": 16.5
},
"cached_input": {
"type": [
"number",
"null"
],
"description": "Por millón de tokens de la instrucción leídos de la caché.",
"example": 0.33
},
"image": {
"type": "number",
"description": "Por imagen, en el tamaño predeterminado del modelo.",
"example": 0.0528
},
"image_sizes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"size": {
"type": "string"
},
"image": {
"type": "number"
}
},
"required": [
"size",
"image"
]
},
"description": "Por imagen en cada tamaño que indica el modelo, primero el más pequeño. Un tamaño intermedio cuesta lo mismo que el siguiente más grande."
},
"characters": {
"type": "number",
"description": "Por millón de caracteres de entrada.",
"example": 16.5
}
},
"description": "Dólares estadounidenses, con los campos que corresponden al tipo de modelo. Los modelos de texto, y los de imagen sin precio por imagen, se cobran por millón de tokens; algunos cobran más por instrucciones muy largas. Los modelos de imagen se cobran por imagen y los de voz por carácter."
},
"ChatCompletion": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"object": {
"type": "string",
"enum": [
"chat.completion"
]
},
"created": {
"type": "integer"
},
"model": {
"type": "string"
},
"choices": {
"type": "array",
"items": {
"type": "object",
"properties": {
"index": {
"type": "integer"
},
"message": {
"type": "object",
"properties": {
"role": {
"type": "string"
},
"content": {
"type": [
"string",
"null"
]
}
},
"required": [
"role",
"content"
],
"additionalProperties": {}
},
"finish_reason": {
"type": [
"string",
"null"
]
}
},
"required": [
"index",
"message",
"finish_reason"
],
"additionalProperties": {}
}
},
"usage": {
"$ref": "#/components/schemas/Usage"
}
},
"required": [
"id",
"object",
"created",
"model",
"choices",
"usage"
],
"additionalProperties": {}
},
"Usage": {
"type": "object",
"properties": {
"prompt_tokens": {
"type": "integer"
},
"completion_tokens": {
"type": "integer"
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.00042
}
},
"required": [
"prompt_tokens",
"completion_tokens",
"cost"
],
"additionalProperties": {}
},
"ChatCompletionRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de `GET /v1/ai/models`.",
"example": "anthropic/claude-sonnet-5"
},
"messages": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ChatMessage"
},
"minItems": 1
},
"stream": {
"type": "boolean",
"description": "Envía la respuesta como eventos enviados por el servidor mientras se escribe. El último evento antes de `[DONE]` indica el uso y el costo."
},
"max_completion_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "La respuesta más larga permitida, en tokens. Sin este campo, un equipo sin recarga automática puede recibir un límite menor que sus créditos cubran."
},
"max_tokens": {
"type": "integer",
"exclusiveMinimum": 0,
"description": "Nombre anterior de `max_completion_tokens`."
},
"temperature": {
"type": "number"
},
"tools": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"function"
]
}
},
"required": [
"type"
],
"additionalProperties": {}
},
"description": "Herramientas de tipo función. Otros tipos de herramienta se rechazan."
},
"n": {
"type": "integer",
"description": "Solo se admite 1."
}
},
"required": [
"model",
"messages"
],
"additionalProperties": {},
"description": "El formato Chat Completions de OpenAI. Los campos que no aparecen aquí se envían al modelo tal cual, excepto `providerOptions` distinto de `gateway.order`, `gateway.only` y `gateway.sort`, y `user`, que se descartan."
},
"ChatMessage": {
"type": "object",
"properties": {
"role": {
"type": "string",
"minLength": 1,
"example": "user"
},
"content": {
"description": "Texto, o un arreglo de partes de texto, imagen y archivo.",
"example": "Hello"
}
},
"required": [
"role"
],
"additionalProperties": {}
},
"ImageGeneration": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"description": "Segundos desde la época Unix."
},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"b64_json": {
"type": "string",
"description": "La imagen, codificada en base64. Normalmente un PNG."
}
},
"required": [
"b64_json"
]
}
},
"usage": {
"$ref": "#/components/schemas/ImageUsage"
}
},
"required": [
"created",
"data",
"usage"
]
},
"ImageUsage": {
"type": "object",
"properties": {
"images": {
"type": "integer",
"description": "Imágenes creadas."
},
"input_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"output_tokens": {
"type": "integer",
"description": "Para los modelos que se cobran por tokens."
},
"total_tokens": {
"type": "integer"
},
"cost": {
"type": "number",
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses. Siempre 0 con una clave de prueba.",
"example": 0.0528
}
},
"required": [
"images",
"cost"
]
},
"ImageGenerationRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de imagen de `GET /v1/ai/models?type=image`.",
"example": "bfl/flux-2-pro"
},
"prompt": {
"type": "string",
"minLength": 1,
"maxLength": 32000,
"description": "Lo que quieres que dibuje.",
"example": "A lighthouse on a cliff at dawn, watercolor"
},
"n": {
"type": "integer",
"minimum": 1,
"maximum": 4,
"description": "Cuántas imágenes crear, hasta 4. El valor predeterminado es 1. Se cobra cada una."
},
"size": {
"type": "string",
"pattern": "^\\d{2,5}x\\d{2,5}$",
"description": "Ancho y alto en píxeles, como `1024x1024`. Los tamaños que admite cada modelo varían.",
"example": "1024x1024"
},
"aspect_ratio": {
"type": "string",
"pattern": "^\\d{1,3}:\\d{1,3}$",
"description": "Proporción entre ancho y alto, como `16:9`, para los modelos que usan una proporción en lugar de un tamaño.",
"example": "16:9"
},
"seed": {
"type": "integer",
"description": "Hace que los resultados se puedan repetir, en los modelos que lo admiten."
},
"response_format": {
"type": "string",
"enum": [
"b64_json"
],
"description": "Solo `b64_json`: las imágenes se devuelven en base64, nunca como enlaces."
}
},
"required": [
"model",
"prompt"
],
"additionalProperties": {},
"description": "El formato Images de OpenAI. Los demás campos, como `quality` o `style`, se ignoran."
},
"SpeechRequest": {
"type": "object",
"properties": {
"model": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "Un ID de modelo de voz de `GET /v1/ai/models?type=speech`.",
"example": "openai/tts-1"
},
"input": {
"type": "string",
"minLength": 1,
"maxLength": 5000,
"description": "El texto que se leerá, hasta 5000 caracteres. Se cobra cada carácter.",
"example": "Your order shipped this morning."
},
"voice": {
"type": "string",
"minLength": 1,
"maxLength": 200,
"description": "La voz que se usará. Cada proveedor tiene las suyas, como `alloy` en OpenAI. Sin este campo, la predeterminada del modelo.",
"example": "alloy"
},
"response_format": {
"type": "string",
"enum": [
"mp3",
"opus",
"aac",
"flac",
"wav",
"pcm"
],
"description": "El formato del audio. Los modelos que no pueden generarlo responden en el suyo, así que lee el encabezado `Content-Type`."
},
"instructions": {
"type": "string",
"maxLength": 4096,
"description": "Cómo debe hablar, por ejemplo el tono o el ritmo, en los modelos que lo admiten."
},
"speed": {
"type": "number",
"minimum": 0.25,
"maximum": 4,
"description": "De 0.25 a 4. El valor predeterminado es 1, en los modelos que lo admiten."
},
"language": {
"type": "string",
"minLength": 2,
"maxLength": 35,
"description": "El idioma del texto, como `en`, cuando importa."
}
},
"required": [
"model",
"input"
],
"additionalProperties": {},
"description": "El formato de voz de OpenAI. Los demás campos se ignoran."
}
},
"parameters": {}
},
"paths": {
"/v1/ai/models": {
"get": {
"operationId": "listModels",
"tags": [
"AI"
],
"summary": "Listar modelos",
"description": "Devuelve todos los modelos que puedes usar, primero los más recientes, con su precio.",
"security": [
{
"apiKey": []
}
],
"parameters": [
{
"schema": {
"type": "string",
"enum": [
"text",
"image",
"speech"
],
"description": "Solo los modelos de este tipo."
},
"required": false,
"description": "Solo los modelos de este tipo.",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "Los modelos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ModelList"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "No se pudo leer la lista de modelos. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/chat/completions": {
"post": {
"operationId": "createChatCompletion",
"tags": [
"AI"
],
"summary": "Crear una respuesta de chat",
"description": "Envía mensajes a un modelo y devuelve su respuesta, en el formato Chat Completions de OpenAI. Con una clave de prueba la respuesta es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletionRequest"
}
}
}
},
"responses": {
"200": {
"description": "La respuesta del modelo o, con `stream: true`, un flujo de eventos `chat.completion.chunk`.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ChatCompletion"
}
},
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/images/generations": {
"post": {
"operationId": "createImage",
"tags": [
"AI"
],
"summary": "Crear una imagen",
"description": "Crea imágenes a partir de una instrucción, en el formato Images de OpenAI. Con una clave de prueba la imagen es de ejemplo y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGenerationRequest"
}
}
}
},
"responses": {
"200": {
"description": "Las imágenes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ImageGeneration"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/v1/ai/audio/speech": {
"post": {
"operationId": "createSpeech",
"tags": [
"AI"
],
"summary": "Crear voz",
"description": "Lee un texto en voz alta y devuelve el archivo de audio. Con una clave de prueba el audio es un segundo de silencio, en WAV, y no se cobra nada.",
"security": [
{
"apiKey": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SpeechRequest"
}
}
}
},
"responses": {
"200": {
"description": "El audio. `Upsky-Cost` indica lo que se cobró, en dólares estadounidenses, y `Upsky-Characters` cuántos caracteres se cobraron.",
"headers": {
"Upsky-Cost": {
"schema": {
"type": "string",
"example": "0.0165"
},
"description": "Lo que se cobró por esta solicitud, en dólares estadounidenses."
},
"Upsky-Characters": {
"schema": {
"type": "string",
"example": "1000"
},
"description": "Caracteres cobrados."
}
},
"content": {
"audio/mpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"audio/wav": {
"schema": {
"type": "string",
"format": "binary"
}
},
"application/octet-stream": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"400": {
"description": "La solicitud no es válida o el modelo la rechazó.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"402": {
"description": "No hay créditos suficientes y ninguna recarga automática pudo cubrirlo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"403": {
"description": "Las claves en vivo necesitan un plan Pro activo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"404": {
"description": "No existe ese modelo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"409": {
"description": "Se está procesando una recarga. Reintenta en unos segundos.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"503": {
"description": "El proveedor del modelo falló. Reintenta con espera progresiva.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}
# Listar archivos (/es/storage/reference/listFiles)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Storage",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/storage/files": {
"get": {
"operationId": "listFiles",
"summary": "Listar archivos",
"description": "Devuelve los archivos de tu equipo, primero los más recientes.",
"parameters": [
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"maximum": 100
}
}
],
"responses": {
"200": {
"description": "Una página de archivos.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data",
"next_cursor"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/File"
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
},
"post": {
"operationId": "createFile",
"summary": "Crear un archivo",
"description": "Reserva un archivo y devuelve el enlace para subirlo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"content_type",
"size"
],
"properties": {
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer",
"description": "Tamaño en bytes."
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo archivo y su enlace de subida.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"upload_url"
],
"properties": {
"upload_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}": {
"get": {
"operationId": "getFile",
"summary": "Obtener un archivo",
"description": "Devuelve un archivo con un enlace de descarga de corta duración.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"download_url"
],
"properties": {
"download_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
},
"delete": {
"operationId": "deleteFile",
"summary": "Eliminar un archivo",
"description": "Elimina un archivo de forma permanente.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"204": {
"description": "El archivo se eliminó."
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}/complete": {
"post": {
"operationId": "completeFileUpload",
"summary": "Completar una subida",
"description": "Marca un archivo como subido cuando el cliente termina de enviarlo.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo subido.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/File"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"parameters": {
"FileId": {
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"File": {
"type": "object",
"required": [
"id",
"name",
"content_type",
"size",
"status"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"uploaded"
]
}
}
}
}
}
}
# Crear un archivo (/es/storage/reference/createFile)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Storage",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/storage/files": {
"get": {
"operationId": "listFiles",
"summary": "Listar archivos",
"description": "Devuelve los archivos de tu equipo, primero los más recientes.",
"parameters": [
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"maximum": 100
}
}
],
"responses": {
"200": {
"description": "Una página de archivos.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data",
"next_cursor"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/File"
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
},
"post": {
"operationId": "createFile",
"summary": "Crear un archivo",
"description": "Reserva un archivo y devuelve el enlace para subirlo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"content_type",
"size"
],
"properties": {
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer",
"description": "Tamaño en bytes."
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo archivo y su enlace de subida.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"upload_url"
],
"properties": {
"upload_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}": {
"get": {
"operationId": "getFile",
"summary": "Obtener un archivo",
"description": "Devuelve un archivo con un enlace de descarga de corta duración.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"download_url"
],
"properties": {
"download_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
},
"delete": {
"operationId": "deleteFile",
"summary": "Eliminar un archivo",
"description": "Elimina un archivo de forma permanente.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"204": {
"description": "El archivo se eliminó."
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}/complete": {
"post": {
"operationId": "completeFileUpload",
"summary": "Completar una subida",
"description": "Marca un archivo como subido cuando el cliente termina de enviarlo.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo subido.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/File"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"parameters": {
"FileId": {
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"File": {
"type": "object",
"required": [
"id",
"name",
"content_type",
"size",
"status"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"uploaded"
]
}
}
}
}
}
}
# Obtener un archivo (/es/storage/reference/getFile)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Storage",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/storage/files": {
"get": {
"operationId": "listFiles",
"summary": "Listar archivos",
"description": "Devuelve los archivos de tu equipo, primero los más recientes.",
"parameters": [
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"maximum": 100
}
}
],
"responses": {
"200": {
"description": "Una página de archivos.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data",
"next_cursor"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/File"
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
},
"post": {
"operationId": "createFile",
"summary": "Crear un archivo",
"description": "Reserva un archivo y devuelve el enlace para subirlo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"content_type",
"size"
],
"properties": {
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer",
"description": "Tamaño en bytes."
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo archivo y su enlace de subida.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"upload_url"
],
"properties": {
"upload_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}": {
"get": {
"operationId": "getFile",
"summary": "Obtener un archivo",
"description": "Devuelve un archivo con un enlace de descarga de corta duración.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"download_url"
],
"properties": {
"download_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
},
"delete": {
"operationId": "deleteFile",
"summary": "Eliminar un archivo",
"description": "Elimina un archivo de forma permanente.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"204": {
"description": "El archivo se eliminó."
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}/complete": {
"post": {
"operationId": "completeFileUpload",
"summary": "Completar una subida",
"description": "Marca un archivo como subido cuando el cliente termina de enviarlo.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo subido.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/File"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"parameters": {
"FileId": {
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"File": {
"type": "object",
"required": [
"id",
"name",
"content_type",
"size",
"status"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"uploaded"
]
}
}
}
}
}
}
# Eliminar un archivo (/es/storage/reference/deleteFile)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Storage",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/storage/files": {
"get": {
"operationId": "listFiles",
"summary": "Listar archivos",
"description": "Devuelve los archivos de tu equipo, primero los más recientes.",
"parameters": [
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"maximum": 100
}
}
],
"responses": {
"200": {
"description": "Una página de archivos.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data",
"next_cursor"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/File"
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
},
"post": {
"operationId": "createFile",
"summary": "Crear un archivo",
"description": "Reserva un archivo y devuelve el enlace para subirlo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"content_type",
"size"
],
"properties": {
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer",
"description": "Tamaño en bytes."
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo archivo y su enlace de subida.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"upload_url"
],
"properties": {
"upload_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}": {
"get": {
"operationId": "getFile",
"summary": "Obtener un archivo",
"description": "Devuelve un archivo con un enlace de descarga de corta duración.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"download_url"
],
"properties": {
"download_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
},
"delete": {
"operationId": "deleteFile",
"summary": "Eliminar un archivo",
"description": "Elimina un archivo de forma permanente.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"204": {
"description": "El archivo se eliminó."
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}/complete": {
"post": {
"operationId": "completeFileUpload",
"summary": "Completar una subida",
"description": "Marca un archivo como subido cuando el cliente termina de enviarlo.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo subido.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/File"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"parameters": {
"FileId": {
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"File": {
"type": "object",
"required": [
"id",
"name",
"content_type",
"size",
"status"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"uploaded"
]
}
}
}
}
}
}
# Completar una subida (/es/storage/reference/completeFileUpload)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Storage",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/storage/files": {
"get": {
"operationId": "listFiles",
"summary": "Listar archivos",
"description": "Devuelve los archivos de tu equipo, primero los más recientes.",
"parameters": [
{
"name": "cursor",
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"maximum": 100
}
}
],
"responses": {
"200": {
"description": "Una página de archivos.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data",
"next_cursor"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/File"
}
},
"next_cursor": {
"type": [
"string",
"null"
]
}
}
}
}
}
}
}
},
"post": {
"operationId": "createFile",
"summary": "Crear un archivo",
"description": "Reserva un archivo y devuelve el enlace para subirlo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"content_type",
"size"
],
"properties": {
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer",
"description": "Tamaño en bytes."
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo archivo y su enlace de subida.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"upload_url"
],
"properties": {
"upload_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}": {
"get": {
"operationId": "getFile",
"summary": "Obtener un archivo",
"description": "Devuelve un archivo con un enlace de descarga de corta duración.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo.",
"content": {
"application/json": {
"schema": {
"allOf": [
{
"$ref": "#/components/schemas/File"
},
{
"type": "object",
"required": [
"download_url"
],
"properties": {
"download_url": {
"type": "string",
"format": "uri"
}
}
}
]
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
},
"delete": {
"operationId": "deleteFile",
"summary": "Eliminar un archivo",
"description": "Elimina un archivo de forma permanente.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"204": {
"description": "El archivo se eliminó."
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/storage/files/{id}/complete": {
"post": {
"operationId": "completeFileUpload",
"summary": "Completar una subida",
"description": "Marca un archivo como subido cuando el cliente termina de enviarlo.",
"parameters": [
{
"$ref": "#/components/parameters/FileId"
}
],
"responses": {
"200": {
"description": "El archivo subido.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/File"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"parameters": {
"FileId": {
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"File": {
"type": "object",
"required": [
"id",
"name",
"content_type",
"size",
"status"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"content_type": {
"type": "string"
},
"size": {
"type": "integer"
},
"status": {
"type": "string",
"enum": [
"pending",
"uploaded"
]
}
}
}
}
}
}
# Listar sitios (/es/hosting/reference/listSites)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Hosting",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/hosting/sites": {
"get": {
"operationId": "listSites",
"summary": "Listar sitios",
"description": "Devuelve los sitios de tu equipo.",
"responses": {
"200": {
"description": "Tus sitios.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Site"
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "createSite",
"summary": "Crear un sitio",
"description": "Crea un sitio nuevo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"framework"
],
"properties": {
"name": {
"type": "string"
},
"framework": {
"type": "string",
"examples": [
"nextjs",
"wordpress"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo sitio.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Site"
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/sites/{id}/deployments": {
"post": {
"operationId": "createDeployment",
"summary": "Crear un despliegue",
"description": "Compila y publica una nueva versión de un sitio.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"source"
],
"properties": {
"source": {
"type": "object",
"required": [
"type",
"ref"
],
"properties": {
"type": {
"type": "string",
"enum": [
"git"
]
},
"ref": {
"type": "string",
"examples": [
"main"
]
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/deployments/{id}": {
"get": {
"operationId": "getDeployment",
"summary": "Obtener un despliegue",
"description": "Devuelve un despliegue y su estado actual.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "El despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"Site": {
"type": "object",
"required": [
"id",
"name",
"framework"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"framework": {
"type": "string"
}
}
},
"Deployment": {
"type": "object",
"required": [
"id",
"site_id",
"status"
],
"properties": {
"id": {
"type": "string"
},
"site_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"building",
"ready",
"failed"
]
},
"url": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
}
}
}
# Crear un sitio (/es/hosting/reference/createSite)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Hosting",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/hosting/sites": {
"get": {
"operationId": "listSites",
"summary": "Listar sitios",
"description": "Devuelve los sitios de tu equipo.",
"responses": {
"200": {
"description": "Tus sitios.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Site"
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "createSite",
"summary": "Crear un sitio",
"description": "Crea un sitio nuevo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"framework"
],
"properties": {
"name": {
"type": "string"
},
"framework": {
"type": "string",
"examples": [
"nextjs",
"wordpress"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo sitio.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Site"
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/sites/{id}/deployments": {
"post": {
"operationId": "createDeployment",
"summary": "Crear un despliegue",
"description": "Compila y publica una nueva versión de un sitio.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"source"
],
"properties": {
"source": {
"type": "object",
"required": [
"type",
"ref"
],
"properties": {
"type": {
"type": "string",
"enum": [
"git"
]
},
"ref": {
"type": "string",
"examples": [
"main"
]
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/deployments/{id}": {
"get": {
"operationId": "getDeployment",
"summary": "Obtener un despliegue",
"description": "Devuelve un despliegue y su estado actual.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "El despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"Site": {
"type": "object",
"required": [
"id",
"name",
"framework"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"framework": {
"type": "string"
}
}
},
"Deployment": {
"type": "object",
"required": [
"id",
"site_id",
"status"
],
"properties": {
"id": {
"type": "string"
},
"site_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"building",
"ready",
"failed"
]
},
"url": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
}
}
}
# Crear un despliegue (/es/hosting/reference/createDeployment)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Hosting",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/hosting/sites": {
"get": {
"operationId": "listSites",
"summary": "Listar sitios",
"description": "Devuelve los sitios de tu equipo.",
"responses": {
"200": {
"description": "Tus sitios.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Site"
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "createSite",
"summary": "Crear un sitio",
"description": "Crea un sitio nuevo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"framework"
],
"properties": {
"name": {
"type": "string"
},
"framework": {
"type": "string",
"examples": [
"nextjs",
"wordpress"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo sitio.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Site"
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/sites/{id}/deployments": {
"post": {
"operationId": "createDeployment",
"summary": "Crear un despliegue",
"description": "Compila y publica una nueva versión de un sitio.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"source"
],
"properties": {
"source": {
"type": "object",
"required": [
"type",
"ref"
],
"properties": {
"type": {
"type": "string",
"enum": [
"git"
]
},
"ref": {
"type": "string",
"examples": [
"main"
]
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/deployments/{id}": {
"get": {
"operationId": "getDeployment",
"summary": "Obtener un despliegue",
"description": "Devuelve un despliegue y su estado actual.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "El despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"Site": {
"type": "object",
"required": [
"id",
"name",
"framework"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"framework": {
"type": "string"
}
}
},
"Deployment": {
"type": "object",
"required": [
"id",
"site_id",
"status"
],
"properties": {
"id": {
"type": "string"
},
"site_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"building",
"ready",
"failed"
]
},
"url": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
}
}
}
# Obtener un despliegue (/es/hosting/reference/getDeployment)
{
"openapi": "3.2.0",
"info": {
"title": "API de Upsky Hosting",
"version": "0.1.0"
},
"servers": [
{
"url": "https://api.upsky.dev/v1"
}
],
"security": [
{
"apiKey": []
}
],
"paths": {
"/hosting/sites": {
"get": {
"operationId": "listSites",
"summary": "Listar sitios",
"description": "Devuelve los sitios de tu equipo.",
"responses": {
"200": {
"description": "Tus sitios.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"data"
],
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Site"
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "createSite",
"summary": "Crear un sitio",
"description": "Crea un sitio nuevo.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"name",
"framework"
],
"properties": {
"name": {
"type": "string"
},
"framework": {
"type": "string",
"examples": [
"nextjs",
"wordpress"
]
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo sitio.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Site"
}
}
}
},
"400": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/sites/{id}/deployments": {
"post": {
"operationId": "createDeployment",
"summary": "Crear un despliegue",
"description": "Compila y publica una nueva versión de un sitio.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"source"
],
"properties": {
"source": {
"type": "object",
"required": [
"type",
"ref"
],
"properties": {
"type": {
"type": "string",
"enum": [
"git"
]
},
"ref": {
"type": "string",
"examples": [
"main"
]
}
}
}
}
}
}
}
},
"responses": {
"201": {
"description": "El nuevo despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/hosting/deployments/{id}": {
"get": {
"operationId": "getDeployment",
"summary": "Obtener un despliegue",
"description": "Devuelve un despliegue y su estado actual.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "El despliegue.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Deployment"
}
}
}
},
"404": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer"
}
},
"responses": {
"Error": {
"description": "La solicitud falló.",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message",
"request_id"
],
"properties": {
"code": {
"type": "string"
},
"message": {
"type": "string"
},
"request_id": {
"type": "string"
}
}
}
}
}
}
}
}
},
"schemas": {
"Site": {
"type": "object",
"required": [
"id",
"name",
"framework"
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"framework": {
"type": "string"
}
}
},
"Deployment": {
"type": "object",
"required": [
"id",
"site_id",
"status"
],
"properties": {
"id": {
"type": "string"
},
"site_id": {
"type": "string"
},
"status": {
"type": "string",
"enum": [
"queued",
"building",
"ready",
"failed"
]
},
"url": {
"type": [
"string",
"null"
],
"format": "uri"
}
}
}
}
}
}
# Obtener tu equipo (/es/platform/reference/getTeam)
{
"openapi": "3.2.0",
"info": {
"title": "API de la plataforma Upsky",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.upsky.dev"
}
],
"components": {
"securitySchemes": {
"apiKey": {
"type": "http",
"scheme": "bearer",
"description": "Tu clave de API, que empieza con upsky_test_ o upsky_live_."
}
},
"schemas": {
"Team": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "El ID del equipo.",
"example": "0b9f6a52-6d0c-4a3e-9d1e-3f4b2a7c8d90"
},
"name": {
"type": "string",
"description": "El nombre del equipo.",
"example": "Acme"
},
"livemode": {
"type": "boolean",
"description": "Indica si esta solicitud usó una clave en vivo. Las claves de prueba nunca generan cargos ni ejecutan verificaciones reales."
}
},
"required": [
"id",
"name",
"livemode"
]
},
"Error": {
"type": "object",
"properties": {
"error": {
"type": "object",
"properties": {
"code": {
"type": "string",
"enum": [
"invalid_request",
"unauthorized",
"insufficient_credits",
"pro_required",
"account_suspended",
"forbidden",
"not_found",
"method_not_allowed",
"conflict",
"idempotency_key_reused",
"request_in_progress",
"retry_later",
"payload_too_large",
"unsupported_media_type",
"rate_limited",
"internal_error",
"service_unavailable"
],
"description": "Un código estable para decidir qué hacer.",
"example": "unauthorized"
},
"message": {
"type": "string",
"description": "Qué salió mal, para que lo lea una persona.",
"example": "The API key is not valid or has been revoked."
},
"request_id": {
"type": "string",
"description": "Inclúyelo cuando contactes a soporte.",
"example": "req_3f9a1c0b7d2e4a6f8b1c2d3e"
}
},
"required": [
"code",
"message",
"request_id"
]
}
},
"required": [
"error"
]
}
},
"parameters": {}
},
"paths": {
"/v1/platform/team": {
"get": {
"operationId": "getTeam",
"tags": [
"Platform"
],
"summary": "Obtener tu equipo",
"description": "Devuelve el equipo al que pertenece esta clave de API.",
"security": [
{
"apiKey": []
}
],
"responses": {
"200": {
"description": "El equipo.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Team"
}
}
}
},
"401": {
"description": "Falta la clave de API, no es válida o fue revocada.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
},
"429": {
"description": "Tu equipo superó su límite de solicitudes.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"webhooks": {}
}