API pública de Vendty para integraciones con partners, ERPs y aplicaciones a medida. REST · JSON · OAuth2 (Personal Access Tokens con scopes).
https://api-pub.vendty.com/v1/v1/me), health — en producciónUna vez publicada una versión bajo /v1/, el contrato se preserva: no se eliminan endpoints, no se quitan campos, no se cambian tipos. Los cambios incompatibles van a /v2/.
per_page hasta 200, links.next para iterar).?updated_since.Si tienes problemas con tu integración: incluye siempre el header X-Request-Id de la respuesta cuando contactes a tu representante de Vendty. Con ese ID ubicamos la llamada exacta en menos de un minuto.
En 5 minutos haces tu primera llamada autenticada a la API y confirmas que tus credenciales funcionan.
ApiClient para tu negocio y te entrega un token. Los tokens se muestran una sola vez al emitirlos — guárdalo en un lugar seguro (gestor de contraseñas, vault, no en el repositorio de tu aplicación).curl o cualquier cliente HTTP.curl -s https://api-pub.vendty.com/v1/me \
-H "Authorization: Bearer TU_TOKEN_AQUI"
Respuesta esperada (HTTP 200):
{
"data": {
"id": "8d3b1bff-…",
"name": "Mi aplicación",
"rate_tier": "basic",
"scopes": [],
"allowed_origins": [],
"created_at": "2026-05-11T18:35:00+00:00"
},
"meta": { "request_id": "f7ad…" }
}
Si recibes 401 unauthenticated → el token está mal, expiró o fue revocado. Contacta a Vendty.
Si recibes 403 tenant_not_enabled → tu negocio aún no ha completado el alta para la API. Contacta a Vendty.
El campo data.scopes te dice qué puede hacer tu token. Los permisos más comunes:
| Scope | Permite |
|---|---|
products:read |
Leer el catálogo de productos |
customers:read |
Leer los clientes (terceros) |
sales:read |
Leer las ventas |
sales:write |
Crear ventas |
reports:read |
Leer informes agregados |
Si el arreglo data.scopes viene vacío, tu token solo puede llamar a /v1/me y /v1/health. Para usar endpoints de negocio pídele a Vendty un token con los scopes que necesitas.
Todas las respuestas incluyen:
| Header | Significado |
|---|---|
X-Request-Id |
Identificador único de esta petición — úsalo al reportar incidencias |
X-RateLimit-Limit |
Límite por minuto de tu plan |
X-RateLimit-Remaining |
Cuántas llamadas te quedan en la ventana actual |
X-RateLimit-Reset |
Marca de tiempo (Unix) en que se reinicia el contador |
Idempotency-KeyCada POST, PATCH, PUT o DELETE debe llevar el header Idempotency-Key con un UUID v4. Si repites la misma llamada con la misma key (dentro de 24 h) recibes la respuesta original — sirve para reintentar de forma segura ante caídas de red.
curl -X POST https://api-pub.vendty.com/v1/customers \
-H "Authorization: Bearer TU_TOKEN_AQUI" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"identification_type": "NIT",
"identification": "900123456",
"business_name": "Distribuidora ACME SAS",
"country": "CO"
}'
Nota: los endpoints de escritura llegan con Phase 2/3. En este momento (Phase 1) solo están disponibles las lecturas de productos y categorías.
Todos los errores siguen el mismo formato:
{
"error": {
"code": "validation_failed",
"message": "La información proporcionada no es válida.",
"details": { "fields": { "email": ["El campo email debe ser una dirección válida."] } },
"request_id": "f7ad…"
}
}
Programa tu lógica contra error.code (estable, legible por máquina). El campo error.message es para humanos y puede cambiar.
Toda llamada a /v1/* (excepto /v1/health) debe llevar un token Bearer de Passport en el header Authorization:
Authorization: Bearer tu_token
Los tokens los emite Vendty — no hay endpoint público de registro ni de login. Para obtener un token, contacta a tu representante de Vendty.
Un token tiene:
ApiClient) a la que pertenece."Producción Siigo 2026") para identificarlo en los logs de auditoría.401 unauthenticated.Puedes inspeccionar tu token activo con GET /v1/me.
Los tokens llevan cero o más scopes del catálogo. Por defecto un token no trae ningún scope — cada endpoint exige al menos uno. Pedir un scope que no tienes devuelve 403 scope_missing.
| Scope | Endpoints |
|---|---|
products:read |
GET /v1/products, GET /v1/categories |
products:write |
POST /v1/products, PATCH /v1/products/{id} (Phase 2) |
customers:read |
GET /v1/customers, GET /v1/customer-groups |
customers:write |
POST /v1/customers, PATCH /v1/customers/{id} |
sales:read |
GET /v1/sales, GET /v1/payment-methods |
sales:write |
POST /v1/sales |
reports:read |
GET /v1/reports/* |
credit-notes:read/write |
(Phase 2) |
voids:write |
(Phase 2) |
inventory:read/write |
(Phase 2) |
webhooks:manage |
(Phase 3) |
| Operación | Quién | Cómo |
|---|---|---|
| Crear cliente | Operador Vendty | php artisan api:client:create (una vez por aplicación) |
| Emitir token | Operador Vendty | php artisan api:token:issue — el bearer se muestra una sola vez |
| Inspeccionar | Tú | GET /v1/me |
| Revocar | Operador Vendty | php artisan api:token:revoke |
| Rotar | Tú vía Vendty | Pides un token nuevo; el viejo se revoca al entregarte el nuevo |
request_id.Si llamas a la API desde un navegador, el origen de tu aplicación debe estar en la lista permitida (allowed_origins) de tu cliente. No se permiten comodines (*). Envía a Vendty los dominios desde los que vas a llamar cuando pidas tu token.
| HTTP | code | Significado |
|---|---|---|
| 401 | unauthenticated |
Token ausente, mal formado, expirado o revocado |
| 403 | scope_missing |
El token no tiene el scope que ese endpoint requiere |
| 403 | client_suspended |
Tu ApiClient está suspendido — contacta a Vendty |
| 403 | tenant_not_enabled |
Tu negocio aún no está habilitado para la API pública |
| 429 | rate_limit_exceeded |
Bajá la tasa de llamadas — ver header Retry-After |
Todas las respuestas no-2xx siguen este formato:
{
"error": {
"code": "<string_estable_legible_por_maquina>",
"message": "<texto_para_humanos>",
"details": { ... opcional ... },
"request_id": "<uuid>"
}
}
error.code es el contrato. Es estable entre versiones. error.message es para humanos y puede cambiar en cualquier momento.
| HTTP | code | Cuándo |
|---|---|---|
| 400 | bad_request |
JSON mal formado o query string inválido |
| 400 | idempotency_key_missing |
POST/PATCH/PUT/DELETE sin header Idempotency-Key |
| 401 | unauthenticated |
Token ausente, mal formado, expirado o revocado |
| 403 | scope_missing |
El token no tiene el scope que el endpoint requiere |
| 403 | client_suspended |
Tu ApiClient está suspendido |
| 403 | tenant_not_enabled |
Tu negocio aún no está habilitado para la API pública |
| 403 | cors_origin_blocked |
El Origin del request no está en tu lista permitida |
| 404 | not_found |
El recurso no existe o no pertenece a tu negocio |
| 409 | conflict |
Conflicto de estado (ejemplo: identificación duplicada) |
| 409 | idempotency_key_reused_with_different_body |
Reusaste un Idempotency-Key con un cuerpo distinto |
| 422 | validation_failed |
El cuerpo de la petición no pasó la validación. details.fields lista los errores por campo |
| 422 | warehouse_required |
include=stock sin warehouse_id |
| 422 | invalid_category_id / invalid_warehouse_id / invalid_customer_id / invalid_seller_id |
Un ID público no se pudo resolver a una entidad del negocio |
| 422 | date_range_too_wide |
Una ventana date_from–date_to excede el límite (90 días en ventas) |
| 422 | invalid_status |
Valor de status no reconocido (válidos: valid, voided, all) |
| 426 | https_required |
La petición llegó por HTTP plano y la API requiere HTTPS |
| 429 | rate_limit_exceeded |
Excediste tu cuota. Mira el header Retry-After |
| 500 | internal_error |
Error inesperado del lado del servidor. Reporta tu request_id |
| 503 | tenant_db_unavailable |
La base de datos del negocio no respondió. Reintentable después de un rato |
Una respuesta con header Idempotent-Replay: true es una copia exacta de la respuesta original asociada al mismo Idempotency-Key. Trátala igual que la primera respuesta.
error.code como única fuente de verdad para tu lógica de cliente. Compara contra el string; no parsees error.message.request_id. Cuando contactes a soporte de Vendty, es el dato más útil.Idempotency-Key que el original. Keys distintos producen efectos distintos (o un 409 si reusas mal).Catálogo de clientes con paginación. Requiere customers:read.
El response NUNCA expone password, remember_token, numero_cuenta ni entidad_bancaria.
| page | integer >= 1 Default: 1 |
| per_page | integer [ 1 .. 200 ] Default: 25 |
| search | string <= 80 characters Substring sobre razón social, nombre comercial y NIT (case-insensitive). |
| identification | string <= 30 characters NIT/identificación exacta. Útil para deduplicación. |
| group_id | string^cg_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
| updated_since | string <date-time> |
| sort | string Default: "-id" Enum: "business_name" "-business_name" "identification" "-identification" "id" "-id" |
{- "data": [
- {
- "id": "string",
- "identification_type": "NIT",
- "identification": "900123456",
- "check_digit": "1",
- "business_name": "Distribuidora ACME SAS",
- "trade_name": "string",
- "email": "user@example.com",
- "phone": "string",
- "mobile": "string",
- "country": "string",
- "state": "string",
- "city": "string",
- "address": "string",
- "notes": "string",
- "online_store": true,
- "group": {
- "id": "string",
- "name": "string"
}
}
], - "meta": {
- "page": 0,
- "per_page": 0,
- "total": 0,
- "total_pages": 0,
- "request_id": "string"
}, - "links": {
}
}| id required | string^c_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
{- "data": {
- "id": "string",
- "identification_type": "NIT",
- "identification": "900123456",
- "check_digit": "1",
- "business_name": "Distribuidora ACME SAS",
- "trade_name": "string",
- "email": "user@example.com",
- "phone": "string",
- "mobile": "string",
- "country": "string",
- "state": "string",
- "city": "string",
- "address": "string",
- "notes": "string",
- "online_store": true,
- "group": {
- "id": "string",
- "name": "string"
}
}
}Requiere AMBOS scopes customers:read y sales:read.
| id required | string^c_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
| page | integer >= 1 Default: 1 |
| per_page | integer [ 1 .. 200 ] Default: 25 |
{- "data": [
- {
- "id": "string",
- "invoice_number": "No45824",
- "status": "valid",
- "invoice_type": "estandar",
- "date": "2019-08-24T14:15:22Z",
- "due_date": "2019-08-24T14:15:22Z",
- "warehouse": {
- "id": "string",
- "name": "string"
}, - "customer": {
- "id": "string",
- "identification_type": "NIT",
- "identification": "900123456",
- "check_digit": "1",
- "business_name": "Distribuidora ACME SAS",
- "trade_name": "string",
- "email": "user@example.com",
- "phone": "string",
- "mobile": "string",
- "country": "string",
- "state": "string",
- "city": "string",
- "address": "string",
- "notes": "string",
- "online_store": true,
- "group": {
- "id": "string",
- "name": "string"
}
}, - "seller": {
- "id": "string",
- "name": "string",
- "email": "user@example.com"
}, - "subtotal": "18000.00",
- "discount_total": "18000.00",
- "tax_total": "18000.00",
- "total": "18000.00",
- "currency": "COP",
- "change": "18000.00",
- "note": "string",
- "details": [
- {
- "id": "string",
- "product_id": "string",
- "product_code": "string",
- "product_name": "string",
- "quantity": 2,
- "unit_price": "18000.00",
- "discount_percent": "0.00",
- "tax_rate": "0.00",
- "subtotal": "18000.00",
- "total": "18000.00"
}
], - "payments": [
- {
- "method": {
- "id": "string",
- "code": "string",
- "name": "string"
}, - "code": "string",
- "amount": "18000.00",
- "change": "18000.00"
}
]
}
], - "meta": {
- "page": 0,
- "per_page": 0,
- "total": 0,
- "total_pages": 0,
- "request_id": "string"
}
}Por defecto solo retorna ventas válidas (status=valid).
Pasa ?status=voided para ver anuladas o ?status=all para ambas.
El rango date_from–date_to no puede exceder 90 días.
| page | integer >= 1 Default: 1 |
| per_page | integer [ 1 .. 200 ] Default: 25 |
| date_from | string <date> |
| date_to | string <date> |
| status | string Default: "valid" Enum: "valid" "voided" "all" |
| customer_id | string^c_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
| warehouse_id | string^wh_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
| seller_id | string^sl_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
| invoice_number | string <= 60 characters |
| include | string Example: include=details,payments Subconjunto de |
| sort | string Default: "-date" Enum: "date" "-date" "total" "-total" "id" "-id" |
{- "data": [
- {
- "id": "string",
- "invoice_number": "No45824",
- "status": "valid",
- "invoice_type": "estandar",
- "date": "2019-08-24T14:15:22Z",
- "due_date": "2019-08-24T14:15:22Z",
- "warehouse": {
- "id": "string",
- "name": "string"
}, - "customer": {
- "id": "string",
- "identification_type": "NIT",
- "identification": "900123456",
- "check_digit": "1",
- "business_name": "Distribuidora ACME SAS",
- "trade_name": "string",
- "email": "user@example.com",
- "phone": "string",
- "mobile": "string",
- "country": "string",
- "state": "string",
- "city": "string",
- "address": "string",
- "notes": "string",
- "online_store": true,
- "group": {
- "id": "string",
- "name": "string"
}
}, - "seller": {
- "id": "string",
- "name": "string",
- "email": "user@example.com"
}, - "subtotal": "18000.00",
- "discount_total": "18000.00",
- "tax_total": "18000.00",
- "total": "18000.00",
- "currency": "COP",
- "change": "18000.00",
- "note": "string",
- "details": [
- {
- "id": "string",
- "product_id": "string",
- "product_code": "string",
- "product_name": "string",
- "quantity": 2,
- "unit_price": "18000.00",
- "discount_percent": "0.00",
- "tax_rate": "0.00",
- "subtotal": "18000.00",
- "total": "18000.00"
}
], - "payments": [
- {
- "method": {
- "id": "string",
- "code": "string",
- "name": "string"
}, - "code": "string",
- "amount": "18000.00",
- "change": "18000.00"
}
]
}
], - "meta": {
- "page": 0,
- "per_page": 0,
- "total": 0,
- "total_pages": 0,
- "request_id": "string"
}, - "links": {
}
}Eager-load automático de customer, seller, warehouse, details, payments (con sus métodos).
| id required | string^s_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
{- "data": {
- "id": "string",
- "invoice_number": "No45824",
- "status": "valid",
- "invoice_type": "estandar",
- "date": "2019-08-24T14:15:22Z",
- "due_date": "2019-08-24T14:15:22Z",
- "warehouse": {
- "id": "string",
- "name": "string"
}, - "customer": {
- "id": "string",
- "identification_type": "NIT",
- "identification": "900123456",
- "check_digit": "1",
- "business_name": "Distribuidora ACME SAS",
- "trade_name": "string",
- "email": "user@example.com",
- "phone": "string",
- "mobile": "string",
- "country": "string",
- "state": "string",
- "city": "string",
- "address": "string",
- "notes": "string",
- "online_store": true,
- "group": {
- "id": "string",
- "name": "string"
}
}, - "seller": {
- "id": "string",
- "name": "string",
- "email": "user@example.com"
}, - "subtotal": "18000.00",
- "discount_total": "18000.00",
- "tax_total": "18000.00",
- "total": "18000.00",
- "currency": "COP",
- "change": "18000.00",
- "note": "string",
- "details": [
- {
- "id": "string",
- "product_id": "string",
- "product_code": "string",
- "product_name": "string",
- "quantity": 2,
- "unit_price": "18000.00",
- "discount_percent": "0.00",
- "tax_rate": "0.00",
- "subtotal": "18000.00",
- "total": "18000.00"
}
], - "payments": [
- {
- "method": {
- "id": "string",
- "code": "string",
- "name": "string"
}, - "code": "string",
- "amount": "18000.00",
- "change": "18000.00"
}
]
}
}Returns the tenant's product catalog. Requires products:read scope.
Default returns only active products; pass active=false for inactive.
Use include=stock (with warehouse_id) to embed stock for one warehouse.
| page | integer >= 1 Default: 1 |
| per_page | integer [ 1 .. 200 ] Default: 25 |
| search | string <= 80 characters Case-insensitive substring search across name and code. |
| category_id | string^cat_[1-9A-HJ-NP-Za-km-z]{8,16}$ Filter by category (opaque public ID). |
| warehouse_id | string^wh_[1-9A-HJ-NP-Za-km-z]{8,16}$ Required when |
| active | boolean Default: true |
| include | string Example: include=stock,category Comma-separated. Supported tokens: |
| updated_since | string <date-time> Returns products created/updated at or after this timestamp. Legacy schemas use |
| sort | string Default: "-created_at" Enum: "name" "-name" "price" "-price" "created_at" "-created_at" |
{- "data": [
- {
- "id": "p_nQsdd4SDgjLM",
- "code": "BC001",
- "name": "Café Americano",
- "description": "string",
- "price": "18000.00",
- "currency": "COP",
- "tax_rate": "19.00",
- "active": true,
- "category": {
- "id": "string",
- "name": "string",
- "code": "string",
- "active": true
}, - "stock": [
- {
- "warehouse_id": "string",
- "warehouse_name": "string",
- "quantity": 0
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page": 0,
- "per_page": 0,
- "total": 0,
- "total_pages": 0,
- "request_id": "string"
}, - "links": {
}
}Returns a product the authenticated tenant owns. Returns 404 for IDs from other tenants — never 403 — to avoid cross-tenant existence disclosure.
| id required | string^p_[1-9A-HJ-NP-Za-km-z]{8,16}$ |
{- "data": {
- "id": "p_nQsdd4SDgjLM",
- "code": "BC001",
- "name": "Café Americano",
- "description": "string",
- "price": "18000.00",
- "currency": "COP",
- "tax_rate": "19.00",
- "active": true,
- "category": {
- "id": "string",
- "name": "string",
- "code": "string",
- "active": true
}, - "stock": [
- {
- "warehouse_id": "string",
- "warehouse_name": "string",
- "quantity": 0
}
], - "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}