Documentación técnica · API para apps externas

API Tienda Capta

Su app vende con su propio catálogo (productos, precios, imágenes) y cobra a crédito con vales Capta. Ustedes crean el pedido en esta API, el comprador firma sus vales en la app de Capta, ustedes surten y nos reportan el envío. A partir de ahí Capta cobra al comprador. No requiere sesión de usuario: solo una API Key por app.

Autenticación
X-Api-Key: <su llave>
Base URL (sandbox)
https://marketplace.capta.best/api/tienda-externa
Base URL (Capta)
{baseUrl}/api/tienda-externa
GET /plazosPlazos (pagos quincenales) disponibles
GET /disponibleQuién es el comprador y cuánto puede financiar
POST /cotizarMontos por vale y si cabe en el crédito (no guarda nada)
POST /pedidosCrear el pedido (idempotente por externoId)
GET /pedidosSus pedidos con filtros y páginas
GET /pedidos/{externoId}Estado completo de un pedido
POST /pedidos/{externoId}/enviadoReportar el envío (con notasEnvio)
POST /pedidos/{externoId}/cancelarCancelar mientras no haya vales firmados

Flujo del pedido

Tipo cliente (el más común). Cada cambio les llega como webhook firmado.

#QuiénQué pasaWebhook
1Su appGET /disponible y POST /cotizar: muestran al comprador cuánto pagará.
2Su appPOST /pedidos: el pedido nace pendiente. Capta avisa al Enlace.
3EnlaceLo autoriza en su app eligiendo el plan (intereses y pago pueden bajar).pedido.autorizado
4CompradorFirma cada vale en la app de Capta.vale.firmado
pedido.firmado
5Su appSurte y reporta POST /pedidos/{id}/enviado. Empieza el calendario de pagos.pedido.enviado
6CompradorTermina de pagar sus vales.pedido.pagado

Tipo enlace: igual, pero el paso 3 y 4 son uno solo: la Enlace firma todos sus vales a la vez y con eso queda autorizado (llegan juntos pedido.autorizado, un vale.firmado por vale y pedido.firmado).

⚠️

No surtan el pedido hasta recibir pedido.firmado (o ver firmado: true en GET /pedidos/{externoId}). Antes de eso el envío se rechaza.

Cancelación: ustedes pueden cancelar mientras ningún vale esté firmado. El Enlace también puede rechazar el pedido; en los dos casos llega pedido.cancelado.

Conceptos

tipoQuién debeQuién autorizaDónde firmaIntereses
cliente defaultEl cliente, con su crédito y la línea de su EnlaceSu Enlace, eligiendo el planApp Capta de clientesSegún el plan
enlaceLa Enlace, con su crédito personalElla misma, al firmarApp Capta de EnlacesSin intereses (total ÷ pagos)
  • Comprador: se identifica por tipo + celular (10 dígitos; se aceptan espacios, guiones y +52). Debe estar activo en Capta y su Enlace habilitado para recibir pedidos. Si el celular no es de nadie en Capta no se puede vender a crédito: la persona debe darse de alta con un Enlace.
  • Plazo: número de pagos quincenales; cada producto lleva el suyo (uno de GET /plazos). Los productos con el mismo plazo forman un vale; dos plazos = dos vales, y el comprador firma cada uno.
  • Flete: lo mandan ustedes para todo el pedido. Se reparte en partes iguales entre los vales (el último absorbe los centavos) y se financia con los productos.
  • Precios: unitarios, con IVA, en pesos MXN. Se usan tal como los mandan.
  • Crédito: tipo cliente, el capital (productos + flete) debe caber en el crédito del cliente y en la línea de su Enlace; tipo enlace, el total en su crédito personal. Si no cabe, el pedido se rechaza.
  • Plan (tipo cliente): la cotización usa el plan inicial (Tu Decides 20 %). Al autorizar, el Enlace elige el definitivo y los montos pueden bajar; los finales llegan en pedido.autorizado.
  • externoId: su número de pedido, único por app. Si reintentan POST /pedidos con el mismo, se regresa el pedido ya creado: no se duplica.

Autenticación

Cada app tiene una llave. Mándenla en el header X-Api-Key de cada petición. Sin llave o con una llave inválida la respuesta es 401. Todas las peticiones y respuestas son JSON; las exitosas vienen en { "data": ... }.

🔑

La llave se entrega una sola vez por un canal seguro y Capta solo guarda su hash. No la pongan en el código del frontend ni en repositorios; si se pierde o se filtra, pidan una nueva y la anterior se desactiva.

Sandbox

Para desarrollar en cualquier momento, sin depender de que el servidor de pruebas de Capta esté encendido. Los ejemplos de esta guía apuntan al sandbox: https://marketplace.capta.best.

  • Cada llamada se manda primero a la API real de pruebas de Capta. Si no responde, contesta un simulador con las mismas reglas, mensajes y formatos; la cotización usa la misma fórmula (mismos montos al centavo).
  • El header X-Sandbox-Origen dice quién contestó: tunel o simulado. GET https://marketplace.capta.best/sandbox muestra el estado de la conexión con Capta y los compradores de prueba.
  • Los pedidos creados en el simulador solo existen en el simulador, y los que contesta Capta solo en Capta: si la conexión cambia, un pedido del otro lado da 404.
  • Usen su llave real en todas las llamadas, también en las acciones de /sandbox: el sandbox y Capta la validan, y sin ella la respuesta es 401.

Errores

CódigoCuándoCuerpo
400Datos inválidos (campo faltante, formato, filtro){ "message": "..." }
401Sin X-Api-Key o llave inválidavacío
404Comprador o pedido no encontrado{ "statusCode": 404, "message": "..." }
422Regla de negocio: Enlace no habilitado, crédito insuficiente, plazo no disponible, pedido no autorizado o sin firmar, ya no se puede cancelar…{ "statusCode": 422, "message": "..." }
500Error de Capta; reintenten más tarde{ "StatusCode": 500, "Message": "..." }

El message está en español y se puede mostrar al usuario.

GET /api/tienda-externa/plazos

Plazos disponibles

Números de pagos quincenales que se pueden elegir para cada producto.

curl
curl "https://marketplace.capta.best/api/tienda-externa/plazos" \
  -H "X-Api-Key: $CAPTA_API_KEY"
Respuesta 200
{ "data": [8, 10, 12, 14, 18] }
GET /api/tienda-externa/disponible

Comprador y crédito disponible

Quién es el comprador, si su Enlace puede recibir pedidos y cuánto puede financiar.

curl
curl "https://marketplace.capta.best/api/tienda-externa/disponible?celular=6670000001&tipo=cliente" \
  -H "X-Api-Key: $CAPTA_API_KEY"
ParámetroTipoDescripción
celularrequerido10 dígitos (se aceptan espacios, guiones y +52).
tipoopcionalcliente (default) o enlace.
Respuesta 200
{
  "data": {
    "tipo": "cliente",
    "clienteId": 800001,
    "nombre": "MARIA PRUEBA LOPEZ",
    "enlaceId": 9001,
    "enlaceNombre": "ANA ENLACE PRUEBA",
    "puedeRecibirPedidos": true,
    "disponible": 15000.00,
    "disponibleCliente": 15000.00,
    "disponibleEnlace": 50000.00
  }
}

disponible: lo máximo que puede financiar (tipo cliente: capital = productos + flete; tipo enlace: total). puedeRecibirPedidos: false = el pedido se rechazará. En tipo enlace, clienteId y disponibleCliente vienen en null y disponibleEnlace es su crédito personal.

404 si no hay comprador activo con ese celular; 422 si hay más de uno, si el cliente no tiene Enlace o si tipo no es válido.

POST /api/tienda-externa/cotizar

Cotizar

Cuánto pagará el comprador, vale por vale, y si cabe en su crédito. No guarda nada.

curl
curl -X POST "https://marketplace.capta.best/api/tienda-externa/cotizar" \
  -H "X-Api-Key: $CAPTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo": "cliente",
    "celular": "667 000 0001",
    "productos": [
      { "sku": "TV-55",  "nombre": "Pantalla 55\"",    "precio": 8999.00, "imagenUrl": "https://ejemplo.com/tv.jpg", "cantidad": 1, "plazo": 18 },
      { "sku": "SOP-01", "nombre": "Soporte de pared", "precio": 499.00,  "imagenUrl": null,                         "cantidad": 1, "plazo": 12 }
    ],
    "flete": 250.00,
    "transportista": "ESTAFETA"
  }'
CampoTipoDescripción
tipoopcionalcliente (default) o enlace.
celularrequerido10 a 20 caracteres.
productosrequerido1 a 100.
productos[].skurequeridoMáx. 50.
productos[].nombrerequeridoMáx. 300.
productos[].preciorequerido0.01 a 1,000,000. Unitario con IVA.
productos[].imagenUrlopcionalURL http(s), máx. 500.
productos[].cantidadrequerido1 a 9999.
productos[].plazorequeridoUno de GET /plazos.
fleteopcional0 a 100,000. Del pedido completo.
transportistaopcionalMáx. 20.
Respuesta 200
{
  "data": {
    "subtotal": 9498.00,
    "flete": 250.00,
    "intereses": 8379.74,
    "total": 18127.74,
    "pagoQuincenal": 1036.58,
    "disponibleCliente": 15000.00,
    "disponibleEnlace": 50000.00,
    "cabeEnCredito": true,
    "vales": [
      { "plazo": 12, "subtotal": 499.00,  "flete": 125.00, "capital": 624.00,  "intereses": 437.40,  "total": 1061.40,  "pagoQuincenal": 88.45 },
      { "plazo": 18, "subtotal": 8999.00, "flete": 125.00, "capital": 9124.00, "intereses": 7942.34, "total": 17066.34, "pagoQuincenal": 948.13 }
    ]
  }
}

pagoQuincenal del pedido es la suma de los vales mientras corren todos. En tipo enlace el mismo pedido da intereses: 0, total: 9748.00 y pagoQuincenal: 558.89 (52.00 + 506.89).

POST /api/tienda-externa/pedidos

Crear pedido

Lo mismo que cotizar más el número de pedido, la entrega y notas. Nace pendiente y Capta avisa al Enlace para que lo autorice (tipo cliente) o lo firme (tipo enlace).

curl
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos" \
  -H "X-Api-Key: $CAPTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "externoId": "A-100234",
    "tipo": "cliente",
    "celular": "6670000001",
    "productos": [
      { "sku": "TV-55",  "nombre": "Pantalla 55\"",    "precio": 8999.00, "imagenUrl": "https://ejemplo.com/tv.jpg", "cantidad": 1, "plazo": 18 },
      { "sku": "SOP-01", "nombre": "Soporte de pared", "precio": 499.00,  "imagenUrl": null,                         "cantidad": 1, "plazo": 12 }
    ],
    "flete": 250.00,
    "transportista": "ESTAFETA",
    "entrega": {
      "calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro",
      "codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro"
    },
    "notas": "Entregar por la tarde"
  }'
CampoTipoDescripción
externoIdrequeridoSu número de pedido. Máx. 64, único por app.
entrega.callerequeridoMáx. 150.
entrega.numeroExteriorrequeridoMáx. 20.
entrega.numeroInterioropcionalMáx. 20.
entrega.colonia, municipio, estadorequeridoMáx. 100.
entrega.codigoPostalrequerido5 dígitos.
entrega.referenciasopcionalMáx. 200.
notasopcionalMáx. 200.

201 Created: pedido nuevo. 200 OK: ya existía uno con ese externoId y se regresa ese. 422: Enlace no habilitado, crédito insuficiente, plazo no disponible, etc. La respuesta es el pedido, en el formato de GET /pedidos/{externoId}.

GET /api/tienda-externa/pedidos

Lista de pedidos

Sus pedidos, del más reciente al más antiguo. Todos los filtros son opcionales.

curl · pedidos por surtir
curl "https://marketplace.capta.best/api/tienda-externa/pedidos?estatus=autorizado&firmado=true" \
  -H "X-Api-Key: $CAPTA_API_KEY"
ParámetroTipoDescripción
estatusopcionalpendiente, autorizado, enviado, pagado o cancelado.
tipoopcionalcliente o enlace.
firmadoopcionaltrue (todos sus vales firmados) o false.
desde, hastaopcionalFecha del pedido, YYYY-MM-DD, inclusive.
paginaopcionalDesde 1 (default 1).
porPaginaopcional1 a 100 (default 50).
Respuesta 200
{
  "data": {
    "total": 1,
    "pagina": 1,
    "porPagina": 50,
    "pedidos": [
      {
        "externoId": "A-100234",
        "pedidoId": 900001,
        "fechaRegistro": "2026-11-11T10:15:00.123",
        "tipo": "cliente",
        "estatus": "autorizado",
        "firmado": true,
        "comprador": { "nombre": "MARIA PRUEBA LOPEZ", "celular": "6670000001" },
        "enlaceNombre": "ANA ENLACE PRUEBA",
        "entrega": { "calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro", "codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro" },
        "total": 17264.52,
        "pagoQuincenal": 987.22,
        "folios": ["F900001", "F900002"],
        "fechaEnvio": null,
        "notasEnvio": null
      }
    ]
  }
}

total es el número de pedidos que cumplen los filtros en todas las páginas. Filtro inválido: 400.

GET /api/tienda-externa/pedidos/{externoId}

Un pedido

Estado completo del pedido y de cada vale. Es el mismo objeto que regresan crear, enviado y cancelar, y el que viaja en los webhooks.

curl
curl "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234" \
  -H "X-Api-Key: $CAPTA_API_KEY"
Respuesta 200
{
  "data": {
    "externoId": "A-100234",
    "pedidoId": 900001,
    "estatus": "autorizado",
    "tipo": "cliente",
    "clienteId": 800001,
    "enlaceId": 9001,
    "comprador": { "nombre": "MARIA PRUEBA LOPEZ", "celular": "6670000001" },
    "enlaceNombre": "ANA ENLACE PRUEBA",
    "entrega": { "calle": "Av. Obregón", "numeroExterior": "850", "numeroInterior": null, "colonia": "Centro", "codigoPostal": "80000", "municipio": "Culiacán", "estado": "Sinaloa", "referencias": "Portón negro" },
    "notas": "Entregar por la tarde",
    "transportista": "ESTAFETA",
    "subtotal": 9498.00,
    "flete": 250.00,
    "intereses": 7516.52,
    "total": 17264.52,
    "pagoQuincenal": 987.22,
    "planElegido": true,
    "firmado": false,
    "fechaRegistro": "2026-11-11T10:15:00.123",
    "fechaAutorizacion": "2026-11-11T11:02:41.507",
    "fechaEnvio": null,
    "notasEnvio": null,
    "fechaCancelacion": null,
    "motivoCancelacion": null,
    "vales": [
      { "folio": "F900001", "plazo": 12, "subtotal": 499.00,  "flete": 125.00, "total": 1010.88,  "pagoQuincenal": 84.24,  "firmado": true,  "estatus": "activo", "skus": ["SOP-01"] },
      { "folio": "F900002", "plazo": 18, "subtotal": 8999.00, "flete": 125.00, "total": 16253.64, "pagoQuincenal": 902.98, "firmado": false, "estatus": "activo", "skus": ["TV-55"] }
    ]
  }
}
CampoValores
estatuspendiente, autorizado, enviado, pagado, cancelado.
vales[].estatusactivo, canjeado (ya tiene calendario de pagos), pagado, cancelado.
firmadoEl comprador firmó todos los vales.
clienteIdnull en tipo enlace.
compradorEl cliente, o la Enlace en tipo enlace.
FechasHora local de México, sin zona.
📦

entrega es la dirección vigente. Mientras no firme, el cliente puede cambiarla en su app: surtan a esta, no a la que mandaron al crear el pedido.

POST /api/tienda-externa/pedidos/{externoId}/enviado

Reportar el envío

Repórtenlo cuando el pedido salga. Requiere estatus autorizado y todos los vales firmados. Desde este momento corre el calendario de pagos del comprador.

curl
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/enviado" \
  -H "X-Api-Key: $CAPTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "notasEnvio": "ESTAFETA guía 1234567890, sale hoy" }'
CampoTipoDescripción
notasEnvioopcionalMáx. 200. Paquetería, guía o lo que quieran anotar; Capta lo ve en el pedido y regresa en notasEnvio.

Respuesta: el pedido con estatus enviado. Si ya estaba enviado se regresa igual (200) sin cambiar nada: pueden reintentar. 422 si no está autorizado o falta alguna firma.

POST /api/tienda-externa/pedidos/{externoId}/cancelar

Cancelar

Solo mientras el pedido esté pendiente o autorizado y ningún vale esté firmado. Se libera el crédito y se avisa al comprador.

curl
curl -X POST "https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/cancelar" \
  -H "X-Api-Key: $CAPTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "motivo": "Sin existencia" }'

motivo es obligatorio (máx. 500). Respuesta: el pedido con estatus cancelado; si ya estaba cancelado se regresa igual (200). 422 si ya hay un vale firmado o ya se envió.

Webhooks

Capta les manda los cambios de sus pedidos a la URL (HTTPS) que nos indiquen. Junto con la URL les entregamos un secreto para validar la firma.

Petición que reciben
POST {su URL}
Content-Type: application/json
X-Capta-Evento: pedido.autorizado
X-Capta-Evento-Id: 5821
X-Capta-Timestamp: 1794412345
X-Capta-Firma: sha256=3f1c…e9

{ "evento": "pedido.autorizado", "valeFolio": null, "pedido": { …mismo objeto que GET /pedidos/{externoId}… } }
EventoCuándovaleFolio
pedido.autorizadoEl Enlace autorizó y eligió el plan (montos finales).null
vale.firmadoEl comprador firmó un vale.folio del vale
pedido.firmadoYa firmó todos los vales: pueden surtir.null
pedido.enviadoSe registró el envío.null
pedido.pagadoEl comprador terminó de pagar todos los vales.null
pedido.canceladoLo cancelaron ustedes o el Enlace.null
  • Respondan 2xx en menos de 15 segundos. Si tienen trabajo pesado, guárdenlo y procésenlo después.
  • Si fallan, Capta reintenta con espera creciente (1 min, 5 min, 15 min, 30 min, 1 h, 3 h, 6 h…) hasta 8 intentos.
  • Los eventos de un mismo pedido llegan en orden: mientras uno no se entregue, los siguientes de ese pedido esperan.
  • Puede llegar el mismo evento más de una vez: usen X-Capta-Evento-Id para ignorar repetidos.
  • pedido trae el estado al momento del evento (en pedido.pagado, al momento de mandarlo). Guíense por X-Capta-Evento. Si se pierde algo, GET /pedidos/{externoId} siempre tiene el estado actual.

Validar la firma

X-Capta-Firma = sha256= + HMAC-SHA256 en hexadecimal (minúsculas), con su secreto como llave, de {X-Capta-Timestamp}.{cuerpo exacto}. Calculen la firma con el cuerpo crudo (antes de parsear el JSON), compárenla en tiempo constante y rechacen timestamps con más de 5 minutos de diferencia.

Node.js (Express)
const crypto = require('crypto');

app.post('/webhooks/capta', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Capta-Timestamp');
  const firma = req.get('X-Capta-Firma') || '';
  const esperada = 'sha256=' + crypto
    .createHmac('sha256', process.env.CAPTA_WEBHOOK_SECRET)
    .update(`${timestamp}.${req.body.toString('utf8')}`)
    .digest('hex');

  const valida = firma.length === esperada.length
    && crypto.timingSafeEqual(Buffer.from(firma), Buffer.from(esperada));
  const reciente = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
  if (!valida || !reciente) return res.sendStatus(401);

  const { evento, valeFolio, pedido } = JSON.parse(req.body.toString('utf8'));
  // guardar / encolar y responder rápido
  res.sendStatus(200);
});
C#
static bool FirmaValida(string secreto, string timestamp, string cuerpo, string firma)
{
    var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secreto), Encoding.UTF8.GetBytes($"{timestamp}.{cuerpo}"));
    var esperada = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
    return CryptographicOperations.FixedTimeEquals(Encoding.ASCII.GetBytes(esperada), Encoding.ASCII.GetBytes(firma ?? ""))
        && Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp)) <= 300;
}

Compradores de prueba

Solo en el simulador del sandbox. Por el túnel se usan los clientes y Enlaces de pruebas que les pase Capta.

CelulartipoQuiénPara probar
6670000001clienteMARIA PRUEBA LOPEZ (crédito $15,000)El flujo normal
6670000002clienteJUAN PRUEBA SIN TIENDApuedeRecibirPedidos: false; crear → 422
6670000003clienteROSA PRUEBA POCO CREDITO ($500)cabeEnCredito: false; crear → 422
6670000004clientePEDRO PRUEBA SIN ENLACE422 sin Enlace asignado
6670000009cliente(dos clientes con el mismo celular)422 más de un cliente
6660000001enlaceANA ENLACE PRUEBA (crédito personal $8,000)Pedidos tipo enlace
6660000002enlaceLUCIA ENLACE SIN TIENDACrear tipo enlace → 422
cualquier otro404

Acciones del simulador

En la vida real el Enlace autoriza y el comprador firma en las apps de Capta. En el simulador lo disparan ustedes (con su X-Api-Key); cada acción manda el mismo webhook que mandaría Capta (a la URL de webhooks que registraron con Capta, firmado con su secreto) y regresa el pedido.

RutaCuerpoSimula
POST /sandbox/pedidos/{id}/autorizar{ "porcentaje": 16 }
16, 17, 18 o 20 (default)
El Enlace autoriza (tipo cliente) y elige el plan
POST /sandbox/pedidos/{id}/firmar{ "folio": "F900001" } opcionalCliente: firma ese vale o el siguiente. Enlace: firma todo y queda autorizado
POST /sandbox/pedidos/{id}/rechazar{ "motivo": "..." }El Enlace rechaza el pedido
POST /sandbox/pedidos/{id}/cambiar-entrega{ "calle": "...", "codigoPostal": "..." }El cliente cambia la dirección (sin webhook)
POST /sandbox/pedidos/{id}/pagarEl comprador terminó de pagar
GET /sandbox/webhooksÚltimos webhooks y si se entregaron
POST /sandbox/reiniciarBorra los pedidos simulados
Flujo completo en el simulador
H="X-Api-Key: $CAPTA_API_KEY"
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/autorizar -H "$H" -H "Content-Type: application/json" -d '{ "porcentaje": 16 }'
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/firmar -H "$H"
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/firmar -H "$H"
curl -X POST https://marketplace.capta.best/api/tienda-externa/pedidos/A-100234/enviado \
  -H "$H" -H "Content-Type: application/json" -d '{ "notasEnvio": "ESTAFETA 123" }'
curl -X POST https://marketplace.capta.best/sandbox/pedidos/A-100234/pagar -H "$H"