API v1

Ofrece tarjetas ACR Card desde tu propio sitio.

Pega una etiqueta <script> y tus usuarios podrán solicitar cualquiera de las tres tarjetas sin salir de tu página. Si prefieres tu propia interfaz, la misma operación está disponible como API REST.

Cómo funciona

Qué es esto

ACR Card se solicita, no se emite al instante: el visitante envía sus datos, nuestro equipo revisa la solicitud y le manda las instrucciones para completar su tarjeta. Lo que integras es esa toma de solicitud.

El recorrido completo
1. El visitante elige tarjeta y rellena sus datos en el widget
2. El widget llama a POST /v1/card-requests con tu clave pública
3. Recibes el id de la solicitud (req_…) y el visitante ve la confirmación
4. Nuestro equipo la revisa y le envía las instrucciones en 24–72 h
5. Tú consultas el estado cuando quieras con tu clave secreta

Tienes dos formas de integrarlo, y puedes combinarlas:

El widget

Una línea de HTML. Trae el diseño, la validación, los tres niveles y el consentimiento resueltos. Es lo que recomendamos: se integra en una tarde.

La API REST

Tu propio formulario contra nuestro endpoint. Control total del diseño, pero la validación, el consentimiento y su evidencia pasan a ser cosa tuya.

Antes de integrar

Escríbenos a info@acr-pay.com para que demos de alta tu empresa como partner. Te devolvemos tu par de claves y damos de alta los dominios desde los que vas a llamar. Necesitamos además firmar un contrato de tratamiento de datos: vas a enviarnos datos personales de tus usuarios, incluido su documento de identidad.

Widget

Instalar el formulario en tu sitio

Una etiqueta <script> y un contenedor. El widget se monta como un iframe servido desde nuestro dominio, con el mismo diseño que la página de ACR Card, y hace todo el trámite dentro de tu sección: elegir tarjeta, rellenar los datos y recibir la confirmación.

Instalación mínima
<div id="acr-card"></div>

<script src="https://acr-pay.com/embed.js"
        data-key="acr_pk_TU_CLAVE_PUBLICA"
        data-target="#acr-card"></script>

Por qué un iframe y no un fragmento de HTML

El visitante teclea aquí su documento de identidad. Al vivir en un iframe de otro origen, el JavaScript de tu página no puede leer esos campos ni lo que se escribe en ellos. Es lo que nos permite firmar que los datos personales de tus usuarios no pasan por tu sitio — y lo que simplifica el contrato de tratamiento de datos entre tu empresa y la nuestra.

Vista previa

Así se ve

Esto es el widget real, funcionando. Puedes recorrer el flujo entero — elegir una de las tres tarjetas, rellenar los datos y enviar. En esta página el envío no llega a la API y no crea ninguna solicitud.

tu-sitio.com — sección de solicitud
Elige tu tarjeta

Tres tarjetas disponibles.

Gestionado por ACR Card

Configuración

Opciones del widget

Todas se pueden pasar como atributos data-* en la etiqueta del script, o como propiedades del objeto de ACRCard.mount().

Atributos de configuración del widget
CampoTipoDescripción
data-keyobligatoriostringTu clave pública (acr_pk_…). Es visible en el HTML: no uses aquí la clave secreta.
data-targetselector CSSDónde montar el widget. Si se omite, se monta justo donde está la etiqueta <script>.
data-tier'acr' | 'vip' | 'daily'Tarjeta preseleccionada al abrir. Por defecto acr. El visitante puede cambiarla.
data-lang'es' | 'en'Idioma del widget. Por defecto es.
data-accentcolor hexColor de acento de botones y filetes, para casarlo con tu sitio. Los colores propios de cada tarjeta no cambian.
data-min-heightnumberAlto inicial en píxeles antes de la primera medición. Por defecto 420.

Si necesitas controlar cuándo y dónde se monta —por ejemplo dentro de una pestaña que se abre bajo demanda, o en una aplicación de una sola página— usa el montaje manual:

Montaje manual
<script src="https://acr-pay.com/embed.js"></script>
<script>
  const widget = ACRCard.mount('#acr-card', {
    key: 'acr_pk_TU_CLAVE_PUBLICA',
    tier: 'vip',            // tarjeta preseleccionada
    lang: 'es',
    accent: '#1a5a85',      // color de acento, para casarlo con tu sitio
    onStep(step)     { console.log('paso:', step) },
    onComplete(data) { console.log('solicitud enviada:', data.cardType) },
  })

  // widget.destroy() lo retira y libera el escuchador de mensajes
</script>

Sobre el color de acento

accent cambia botones y filetes para que el bloque case con tu sitio. No cambia los colores de las tarjetas: la ACR Card es azul noche y la VIP oro porque son la marca del producto, no decoración. Tampoco aceptamos CSS libre — el widget tiene que verse igual y funcionar igual en todas partes.

Eventos

Medir tu embudo

El widget publica lo que ocurre dentro para que puedas medirlo con tu propia analítica. Los eventos se emiten sobre el contenedor como CustomEvent, y también puedes recibirlos por las funciones onStep y onComplete del montaje manual.

Escuchar los eventos
const contenedor = document.querySelector('#acr-card')

contenedor.addEventListener('acrcard:step', (e) => {
  // e.detail.step → 'tier' | 'form' | 'success'
  gtag('event', 'acr_paso', { paso: e.detail.step })
})

contenedor.addEventListener('acrcard:submitted', (e) => {
  // e.detail.cardType → 'acr' | 'vip' | 'daily'
  // e.detail.duplicate → true si ya existía una solicitud igual
  gtag('event', 'acr_solicitud', { tarjeta: e.detail.cardType })
})

Qué NO sale del iframe

En estos eventos viajan solo la altura del contenido, el nombre del paso y qué tarjeta se pidió. Nunca el nombre, el email, el teléfono ni el documento del solicitante. Si tu integración necesita saber quién solicitó, se consulta desde tu servidor con la clave secreta — nunca desde el navegador.

Requisitos

Política de seguridad de contenido

Si tu sitio envía una cabecera Content-Security-Policy, hay que autorizar nuestro dominio o el widget no cargará. Es el motivo más común de que una integración correcta parezca no funcionar, y el navegador solo lo dice en la consola.

Directivas necesarias
Content-Security-Policy:
  script-src 'self' https://acr-pay.com;
  frame-src  'self' https://acr-pay.com;

Cookies de terceros

El widget no usa cookies. El estado entre pasos vive en memoria del iframe, porque los navegadores bloquean el almacenamiento de terceros y una integración apoyada en cookies se rompería sola en Safari y en Chrome con protección reforzada.

Autenticación

Dos claves, y la diferencia importa

Cada partner recibe un par de claves. Se envían siempre en la cabecera Authorization: Bearer ….

acr_pk_…

Pública

Va en el HTML de tu web. No es un secreto: cualquiera puede leerla en tu código fuente. Lo único que la protege es la lista de dominios autorizados. Solo puede crear solicitudes.

acr_sk_…

Secreta

Solo en tu servidor, en una variable de entorno. Además de crear, puede consultar y listar. Nunca la pongas en el navegador ni la subas al repositorio.

Si la clave secreta se filtra

Revócala desde tu panel de partner y emite otra. Guardamos solo su huella criptográfica, así que no podemos recuperártela ni decirte cuál era: la única salida es sustituirla.

Los dominios de la clave pública se comparan de forma exacta —esquema, dominio y puerto— y no admiten comodines. Si sirves en https://tusitio.com y https://www.tusitio.com, autoriza los dos.

Referencia

Crear una solicitud

POST/api/v1/card-requestsclave pública o secreta

Es el único endpoint que acepta la clave pública, porque es el que usa el widget desde el navegador del visitante.

Parámetros de creación
CampoTipoDescripción
nameobligatoriostringNombre del solicitante. Máximo 80 caracteres.
surnameobligatoriostringApellidos. Máximo 80 caracteres.
documentobligatoriostringDNI o pasaporte. Alfanumérico, de 5 a 20 caracteres.
emailobligatoriostringCorreo del solicitante. Se normaliza a minúsculas.
phoneobligatoriostringTeléfono en formato internacional con prefijo: +34600111222.
card_typeobligatorio'acr' | 'vip' | 'daily'Qué tarjeta se solicita. Consulta /v1/tiers para el catálogo vigente.
external_refstringTu propio identificador para este trámite. Si lo envías, reintentar la misma petición devuelve la solicitud ya creada en vez de duplicarla.
source_urlstringURL de la página desde la que se solicitó. El widget lo rellena solo.
Petición
curl https://acr-pay.com/api/v1/card-requests \
  -H "Authorization: Bearer acr_sk_TU_CLAVE_SECRETA" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ana",
    "surname": "Pérez Gómez",
    "document": "X1234567Z",
    "email": "ana@ejemplo.com",
    "phone": "+34600111222",
    "card_type": "acr",
    "external_ref": "pedido-4821"
  }'
Respuesta 201
{
  "id": "req_9f2a4c8e1b7d3a5f6c0e2d41",
  "object": "card_request",
  "status": "pending",
  "card_type": "acr",
  "cardholder": {
    "name": "Ana",
    "surname": "Pérez Gómez",
    "email": "ana@ejemplo.com",
    "phone": "+34600111222"
  },
  "external_ref": "pedido-4821",
  "source_url": "https://tu-sitio.com/tarjetas",
  "created_at": "2026-09-01T10:24:11.482Z",
  "duplicate": false,
  "request_id": "req_trace_4b1c9e02aa73f5d8"
}

Reintentar sin duplicar

Si envías external_ref, repetir la misma petición tras un fallo de red devuelve la solicitud original con duplicate: true y un 200, en lugar de crear una segunda. Usa el identificador que ya tengas en tu sistema para ese trámite.

Aunque no lo envíes, deduplicamos por persona y nivel: la misma tarjeta para el mismo email o teléfono es siempre una sola solicitud. Si esa solicitud previa la creó otro partner, la respuesta lo indica con status: "duplicate" pero devuelve id: null — no exponemos los identificadores de un partner a otro.

Node.js — desde tu servidor
const res = await fetch('https://acr-pay.com/api/v1/card-requests', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ACR_SECRET_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name, surname, document, email, phone,
    card_type: 'acr',
    external_ref: pedidoId,   // tu identificador: hace el reintento seguro
  }),
})

const solicitud = await res.json()
if (!res.ok) throw new Error(solicitud.error.message)
PHP — desde tu servidor
<?php
$ch = curl_init('https://acr-pay.com/api/v1/card-requests');
curl_setopt_array($ch, [
  CURLOPT_POST           => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER     => [
    'Authorization: Bearer ' . getenv('ACR_SECRET_KEY'),
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'name'         => $nombre,
    'surname'      => $apellidos,
    'document'     => $documento,
    'email'        => $email,
    'phone'        => $telefono,
    'card_type'    => 'acr',
    'external_ref' => $pedidoId,
  ]),
]);

$solicitud = json_decode(curl_exec($ch), true);
Referencia

Consultar y listar

GET/api/v1/card-requests/:idsolo clave secreta
GET/api/v1/card-requestssolo clave secreta

Estas dos devuelven datos personales de tus solicitantes, así que exigen la clave secreta: servirlas a una clave pública las dejaría a la vista de cualquiera que abriese el código fuente de tu página. La consulta filtra siempre por tu partner — nunca podrás leer una solicitud ajena, aunque aciertes el identificador.

Parámetros del listado
CampoTipoDescripción
limitnumberCuántas devolver, de 1 a 100. Por defecto 25.
statusstringFiltra por estado: pending, approved o rejected.
card_typestringFiltra por nivel de tarjeta.
starting_afterstringCursor de la página siguiente. Usa el next_cursor de la respuesta anterior.
Petición
curl "https://acr-pay.com/api/v1/card-requests?limit=25&status=pending" \
  -H "Authorization: Bearer acr_sk_TU_CLAVE_SECRETA"
Respuesta 200
{
  "object": "list",
  "data": [ { "id": "req_9f2a…", "status": "pending", … } ],
  "has_more": true,
  "next_cursor": "Y3JfNDgyMQ",
  "request_id": "req_trace_1a0f…"
}

La paginación es por cursor: si has_more es true, pasa el next_cursor como starting_after en la siguiente llamada. No uses desplazamiento numérico: entran solicitudes nuevas mientras paginas y con offset se repiten o se saltan filas.

El documento de identidad no se devuelve nunca

Ninguna respuesta incluye el campo document, ni siquiera con la clave secreta. Todo el sentido de que el formulario viva en un iframe es que ese dato no pasa por tus sistemas; devolverlo por la API lo entregaría igualmente por la puerta de atrás.

Ciclo de vida

Estados de una solicitud

Estados posibles
CampoTipoDescripción
pendinginicialRecibida, pendiente de revisión por nuestro equipo.
approvedfinalAprobada. Al solicitante se le envían las instrucciones para completar su tarjeta.
rejectedfinalRechazada tras la revisión.
duplicaterespuestaNo es un estado almacenado: aparece al crear cuando ya existe una solicitud igual de otro partner.

Cómo enterarte de un cambio de estado

Hoy se consulta con GET /v1/card-requests/:id. Los webhooks firmados (card_request.approved y card_request.rejected) están en camino; hasta entonces, una consulta diaria de las solicitudes en pending es suficiente para el volumen que maneja cualquier partner.

Errores

Cómo vienen los fallos

Todos los errores tienen la misma forma. Escribe tu lógica contra code, que es estable, y no contra message, que podemos reescribir o traducir.

Forma de un error
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_parameter",
    "message": "El teléfono debe ir en formato internacional, por ejemplo +34600111222.",
    "param": "phone"
  },
  "request_id": "req_trace_9d02f7b1c4e6a380"
}
codeHTTPQué significa
missing_api_key401No enviaste la cabecera Authorization.
invalid_api_key401La clave no existe o fue revocada.
origin_not_allowed403El dominio desde el que llama el navegador no está en la lista de tu clave pública.
secret_key_required403Esa operación necesita la clave secreta; enviaste la pública.
partner_suspended403Tu cuenta de partner está suspendida.
invalid_parameter400Un campo no es válido. El campo concreto viene en param.
invalid_json400El cuerpo no es JSON válido.
resource_missing404No existe ninguna solicitud tuya con ese identificador.
too_many_requests429Superaste las 300 peticiones por minuto de esa clave.
internal_error500Fallo nuestro. Reintenta y escríbenos con el request_id.

request_id

Toda respuesta, correcta o no, incluye un request_id. Guárdalo en tus registros: es lo que nos permite localizar tu petición exacta si algo va mal, en vez de buscar a ciegas por fecha y hora.

El límite es de 300 peticiones por minuto y clave. Un widget en una página con tráfico normal no se acerca; si lo superas de forma sostenida, escríbenos y lo ajustamos.