Crear un comercio
POST /api/merchants registra un comercio nuevo. La petición no espera a que el comercio quede escrito: valida el contenido, lo acepta y devuelve un identificador de proceso con el que seguir el resultado.
La petición
Solicitud
curl --request POST \
--url '{URL_BASE}/api/merchants' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {TU_TOKEN}' \
--header 'Idempotency-Key: 8f1c2d3e4a5b6c7d8e9f0a1b' \
--data @merchant.json
Estructura del cuerpo
El cuerpo describe el estado completo del comercio que quieres registrar. Los campos del comercio viajan en la raíz, al lado de las colecciones.
- Name
Campos del comercio- Type
- varios
- is Required
- REQUERIDO
- Description
Identidad, documento, dirección, representante legal, facturación, agencia de viajes y control comercial. Van directamente en la raíz:
name,brand,document,address… Ver Datos del comercio.
- Name
integrations- Type
- array
- is optional
- Description
Integraciones del comercio con proveedores externos. Ver Integraciones.
- Name
paymentMethods- Type
- array
- is optional
- Description
Medios de pago que el comercio acepta. Ver Medios de pago.
- Name
sites- Type
- array
- is optional
- Description
Sitios de venta del comercio, con sus propias integraciones y medios de pago. Ver Sitios.
- Name
notification- Type
- object
- is optional
- Description
URL donde quieres recibir el resultado del proceso.
Cuerpo mínimo
Estos son todos los campos que Onboarding exige para registrar un comercio. Todo lo demás es opcional.
Cuerpo mínimo
{
"name": "Comercializadora Acme S.A.S.",
"brand": "Acme",
"languages": ["es"],
"currencies": ["COP"],
"document": {
"type": "NIT",
"number": "9001234567"
},
"address": {
"city": "Bogota",
"country": "CO",
"state": 11
},
"legalRepresentative": {
"name": "Ada",
"surname": "Lovelace"
},
"control": {
"reseller": 1
}
}
Cuerpo completo
Este es el cuerpo con todos los campos que la petición acepta. Salvo el mínimo de arriba, cada uno puede omitirse: lo que no envías queda con el valor que Onboarding tenga por defecto.
Los campos del comercio y las colecciones conviven en el mismo nivel. Las colecciones son independientes entre sí —puedes enviar solo sites, solo paymentMethods, ambas o ninguna— y todo lo que envías se registra dentro de la misma operación, de modo que un comercio con sus sitios y sus medios de pago se crea con una sola petición.
Cuerpo completo
{
"name": "Comercializadora Acme S.A.S.",
"brand": "Acme",
"active": true,
"url": "https://acme.example.com",
"incrementType": "IPC",
"mcc": "5411",
"size": "M",
"timezone": "America/Bogota",
"languages": ["es", "en"],
"currencies": ["COP", "USD"],
"document": {
"type": "NIT",
"number": "9001234567"
},
"address": {
"street": "Carrera 43A # 1-50, Torre 2",
"city": "Bogota",
"country": "CO",
"state": 11,
"phone": "6013905000",
"postalCode": "110111"
},
"legalRepresentative": {
"name": "Ada",
"surname": "Lovelace",
"documentType": "CC",
"document": "1020304050"
},
"billing": {
"contact": "Contabilidad Acme",
"mail": "[email protected]",
"registrationNumber": "9001234567-1",
"ciiu": "4791",
"taxType": 1,
"taxRegime": 1,
"organizationType": 1
},
"travelAgency": {
"dispersion": false,
"iata": "ABCD12"
},
"control": {
"reseller": 1,
"seller": 1,
"paymentFacilitator": null
},
"integrations": [
{
"type": "kount",
"settings": {
"sandbox": false,
"website": "ACME",
"merchant": "201000"
}
},
{
"type": "push_notification",
"settings": {
"enabled": true
}
}
],
"paymentMethods": [
{
"code": "CR_VS",
"financialEntity": 7,
"accountNumber": "1234567890",
"accountType": 2,
"commissionModel": "P",
"commissionValue": 2.5,
"order": 1,
"creditRules": {
"minInstallments": 1,
"maxInstallments": 36
},
"settings": {
"merchantCode": "201000",
"terminalNumber": "00000001",
"isEcommerce": true,
"threeDSProvider": "placetopay"
}
}
],
"sites": [
{
"login": "aabbccdd1234567890aabbccdd123456",
"name": "Tienda principal",
"displayName": "Acme",
"type": "INT",
"category": "ECOMMERCE",
"active": true,
"onTest": false,
"currency": "COP",
"language": "es",
"email": "[email protected]",
"customBatch": 1,
"expiration": "2027-01-31 23:59:59",
"productionDate": "2026-09-01",
"integration": {
"allowPartial": false,
"checkoutVersion": "v4",
"connectionMethod": "REDIRECT",
"notificationUrl": "https://acme.example.com/pay/notify",
"analyticsCode": "G-ABCDE12345",
"sourceRestriction": null,
"allowWallet": true,
"checkoutAttemptsLimit": 5,
"returnAdditional": false,
"payerFieldsType": 0
},
"control": {
"minAmount": 1000,
"maxAmount": 5000000,
"allowedDays": 127,
"allowedHours": "ffffffffffffffffffffffffffffffffffffffffff",
"filterPse": false,
"filterByBin": false,
"filterTuya": false,
"filterSafetypay": false,
"prechargeFilter": 0,
"allowedBankCountries": ["CO"],
"exceptionBinsList": ["411111"],
"delegatedAuthorization": null
},
"threeDS": {
"setting": "LOW",
"minAmount": 100000
},
"securityFilters": {
"filterLockedByTries": true,
"forceIPMatch": false,
"onlyWhitelisted": false,
"allowedIssuerCountries": ["CO"],
"allowedIPCountries": ["CO", "US"],
"excludeIPCountry": null,
"allowedCardsDay": 5,
"allowedCardsMonth": 20,
"allowedCardsYear": 60,
"allowedCardsByIP": 10,
"dayMax": 5,
"monthMax": 100,
"dayMaxAmount": 10000000,
"monthMaxAmount": 200000000
},
"operation": {
"disableTax": false,
"calculateTax": true,
"mailCustomer": true,
"mailMerchant": false,
"taxPercentage": 19,
"patternReference": "1234567890"
},
"subscriptionValidation": {
"enable": true,
"amount": 1000
},
"branding": {
"buttonColor": "#1a73e8",
"sidebarColor": "#0b1220"
},
"metadata": {
"contractRef": "AC-2026-0042",
"onboardingBatch": 17
},
"integrations": [
{
"type": "clicktopay_visa",
"settings": {
"enabled": true
}
}
],
"paymentMethods": [
{
"code": "CR_VS",
"financialEntity": 7,
"accountNumber": "1234567890",
"accountType": 2,
"minAmount": 2000,
"maxAmount": 900000,
"commissionModel": "P",
"commissionValue": 2.5,
"order": 1,
"settings": {
"terminalNumber": "00000002"
}
}
]
}
],
"notification": {
"url": "https://acme.example.com/hooks/onboarding"
}
}
Los valores de catálogo —mcc, size, timezone, state, taxType, taxRegime, organizationType, reseller, seller, paymentFacilitator, code, financialEntity, accountType— salen de los catálogos de Placetopay y dependen del ambiente. Los del ejemplo son ilustrativos: pide los tuyos junto con las credenciales, porque un identificador válido en un ambiente no tiene por qué serlo en otro. Además, reseller y seller deben estar habilitados, no solo existir.
Un sitio obliga a mucho más que un comercio
sites es opcional, pero en cuanto envías un sitio casi todo lo suyo pasa a ser obligatorio: name, displayName, type, category, currency, language, email y los bloques integration y control completos —con sus propios allowPartial, checkoutVersion, minAmount, maxAmount, allowedDays, allowedHours, filterPse y filterByBin—. El resto de bloques (threeDS, securityFilters, operation, subscriptionValidation, branding, metadata) sigue siendo opcional. El detalle está en Sitios.
Tres formatos que suelen sorprender:
- El sitio distingue mayúsculas donde la raíz no.
languagesycurrenciesde la raíz aceptan cualquier grafía, perosites[].languageysites[].currencyexigen la del catálogo, que hoy escribe el idioma en minúscula (es) y la moneda en mayúscula (COP). Un"language": "ES"dentro de un sitio responde422— y el mensaje del error te dice la grafía que espera. allowedHoursson 42 caracteres hexadecimales exactos. Una cadena más corta no produce un error: produce un horario equivocado.sites[].paymentMethods[].codesolo puede nombrar un medio de pago que este mismo cuerpo declaró en elpaymentMethodsde la raíz. Al crear no hay nada preexistente a lo que apuntar, porque el comercio todavía no existe.- Dentro del sitio basta el
code. Los datos de cuenta y comisión son obligatorios en elpaymentMethodsde la raíz, pero opcionales dentro de un sitio: lo que no nombras se hereda del medio de pago del comercio. Envía solo lo que deba diferir.
Lo que el ejemplo deja fuera
Un sitio admite además tres credenciales —login, tranKey y md5Hash, de hasta 32 caracteres cada una— que aquí se omiten a propósito. Si no las envías, Onboarding las genera y te las devuelve en el resultado del proceso, que es la forma recomendada de obtenerlas; las que genera son de 16 caracteres. El ejemplo solo conserva login para mostrar dónde va.
Campos que la petición rechaza
Estos campos no son ignorados: si viajan en el cuerpo de una creación, la respuesta es 422 nombrándolos. Casi todos son identificadores que solo tienen sentido cuando el comercio ya existe, así que aparecen al reenviar como petición un cuerpo copiado de una respuesta.
La URL de notificación
Si envías notification.url, Onboarding te avisará en esa dirección cuando el proceso termine, tanto si termina bien como si falla.
- Name
notification.url- Type
- string
- is optional
- Description
URL pública sobre
httpsdonde recibirás el webhook, sin credenciales embebidas.
Onboarding valida solo la forma de la URL en el momento de aceptar la petición. La accesibilidad real del destino se comprueba justo antes de cada envío, de modo que una URL que apunte a una red no pública se acepta pero nunca se entrega. El detalle está en Notificación.
La respuesta
Si el contenido es válido, la respuesta es 202.
- Name
status- Type
- object
- is Required
- REQUERIDO
- Description
Estado de la petición. En el
202siempre esOK, y significa que la petición fue aceptada.
- Name
processId- Type
- string
- is Required
- REQUERIDO
- Description
Identificador del proceso. Consérvalo: es con lo que consultarás el resultado.
- Name
action- Type
- string
- is Required
- REQUERIDO
- Description
La operación pedida. En una creación,
merchant.create.
Además, dos headers:
Respuesta
{
"status": {
"status": "OK",
"reason": null,
"message": "The request was accepted and will be processed shortly.",
"date": "2026-08-17T10:14:03-05:00"
},
"processId": "01kz0609xy2hd666mnx6b0jxpc",
"action": "merchant.create",
"result": null,
"notification": {
"url": "https://acme.example.com/hooks/onboarding",
"state": "PENDING",
"attempts": 0
}
}
Un 202 con status.status en OK responde a «¿aceptaste mi petición?», no a «¿ya existe el comercio?». En ese instante el comercio todavía no está registrado y result es null.
Qué hacer después
- Guarda el
processIdasociado a la operación en tu sistema. - Consulta el proceso con
GET /api/processes/{processId}hasta que la respuesta deje de traerRetry-After. - Guarda
result.merchantIdcuando el proceso termine bien.
Si registraste una URL de notificación, puedes esperar el webhook en lugar de consultar — pero mantén la consulta como respaldo, porque la entrega no está garantizada.
Errores de la llamada
Estos errores impiden que la petición se acepte. Ninguno crea un proceso, y por eso ninguno trae processId.
Un 422 incluye un objeto errors hermano de status, con una entrada por cada campo rechazado. Las claves son las rutas del propio cuerpo que enviaste, de modo que puedes localizar el problema sin traducir nada.
Respuesta de error
{
"status": {
"status": "FAILED",
"reason": 422,
"message": "The given data was invalid.",
"date": "2026-08-17T10:14:03-05:00"
},
"errors": {
"name": ["The name field is required."],
"currencies.0": ["The selected currencies.0 is invalid."]
}
}
La validación no se detiene en el primer error: la respuesta enumera todos los problemas encontrados, para que puedas corregirlos de una vez. El catálogo completo de errores está en Errores.
¿Qué sigue?
- Consultar el proceso
- Datos del comercio
- Referencia de la API — el contrato completo, campo a campo