Saltar al contenido

Endpoints

Endpoints

Nombre

Crear enlaces cortos en lote

Hasta 1.000 enlaces en una petición, con un resultado por enlace y un solo cobro por lo que se creó.

Método HTTP y ruta

POST/api/v6/urlshortener/links/batch

https://developers.hablame.co/api/v6/urlshortener/links/batch

Qué pasa al ejecutarlo

  • Crea datos en tu organización.
  • La llamada se cobra a tu organización.
  • Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).

Qué hace

Crea hasta 1.000 enlaces en una petición. Cada enlace del lote lleva los mismos cuatro campos que el alta de un enlace y pasa exactamente por las mismas comprobaciones: el destino, el alias, el dominio y el vencimiento.

Un enlace inválido no tumba el lote: la respuesta es 200 con un resultado por enlace, en el orden del cuerpo (created, replayed o failed), y cada fallido trae en error.code el mismo código que devolvería el alta de ese enlace sola. Lo que sí es del lote entero se contesta sin crear nada: un cuerpo inválido o con más de 1.000 enlaces (400), la llave usada con otro cuerpo (422), el saldo o un tope de gasto (402), la falta de precio o de moneda (409) y los límites de uso (429).

Un solo cobro por lo que se creó. Se reserva de tu saldo el precio de todos los enlaces válidos a la vez y se cobra solo por los que de verdad se crearon: si de 1.000 se crean 998, se cobran 998. Sin saldo para todos, 402 y no se crea ninguno; manda un lote más pequeño. Con un plan mensual del acortador vigente no se cobra, y los enlaces del lote cuentan en el cupo diario de altas: un lote que no cabe entero se rechaza entero con 429 URLSHORTENER_LINK_QUOTA_EXCEEDED, y el mensaje dice cuántos te quedan hoy.

Disponibilidad y requisitos previos

  • Publicado y disponible en la API v6 de producción.
  • Todas las rutas parten de https://developers.hablame.co.
  • Necesitas una API key de Hablame para llamarlo.

Crea tu clave de API

Autenticación y permisos

  • Envía tu API key en la cabecera Authorization con el esquema Bearer.

Quién puede llamarlo: Una API key con el servicio urlshortener habilitado.

Límites y consideraciones operativas

Límite de uso: 35 solicitudes por segundo.

  • Las 35 solicitudes por segundo son las mismas del alta de un enlace: las dos rutas gastan un solo cupo.
  • Además, hasta 2.100 enlaces por minuto por organización entre todos sus lotes. Un lote que no cabe se rechaza entero.
  • Hasta 1.000 enlaces por petición; el cuerpo, hasta 3 MiB; cada destino, hasta 2048 caracteres.
  • Con plan: los enlaces del lote cuentan en el cupo diario de altas (100.000 por día de Colombia).

Parámetros de ruta

Este endpoint no tiene parámetros de ruta.

Parámetros de consulta

Este endpoint no tiene parámetros de consulta.

Encabezados HTTP

NombreTipoDescripción y reglas
AuthorizationObligatoriostring

Tu API key con el esquema Bearer.

Ejemplo:Bearer hk_TU_API_KEY

Content-TypeObligatoriostring

Tipo del cuerpo que envías.

Valor fijo:application/json

Idempotency-KeyOpcionalstring

Llave de idempotencia opcional del lote (1 a 255 caracteres de A-Za-z0-9_-), única por organización. Si repites el lote con la misma llave y el mismo cuerpo, recibes los enlaces que ya se crearon (replayed) sin crearlos ni cobrarlos otra vez, y lo que no se creó la primera vez se vuelve a intentar. Con otro cuerpo, 422 IDEMPOTENCY_KEY_REUSED. Es independiente de las llaves del alta de un enlace. Ver la guía de idempotencia.

Patrón:^[A-Za-z0-9_-]+$Largo mínimo:1Largo máximo:255Ejemplo:campana-octubre-lote-01

Cuerpo de la petición

Tipo de contenido:application/jsonObligatorio

Solo se acepta links, y en cada enlace solo sus cuatro campos. Uno que no existe se rechaza con 400 nombrando el campo, y el lote no se atiende.

NombreTipoDescripción y reglas
linksObligatorioarray<object>

Los enlaces a crear, de 1 a 1.000. La posición de cada uno es su index en la respuesta.

Mínimo de elementos:1Máximo de elementos:1000

links[].longUrlObligatoriostring

URL de destino, con las mismas reglas del alta de un enlace.

Formato:uriEjemplo:https://example.com/landing?utm_source=sms

links[].domainOpcionalstring

Dominio donde publicarlo. Si lo omites, el predeterminado de tu organización.

Ejemplo:h0b.co

links[].codeOpcionalstring

Alias opcional, con las mismas reglas del alta de un enlace. Dos enlaces del mismo lote con el mismo alias en el mismo dominio: el primero se lo queda y el otro sale failed con ALIAS_TAKEN.

Patrón:^[a-z0-9_-]{3,32}$Ejemplo:promo-octubre

links[].expiresAtOpcionalstring

Vencimiento opcional, siempre futuro. Sin zona horaria se interpreta en hora de Colombia (-05:00).

Formato:date-timeEjemplo:2026-12-31T23:59:59-05:00

Ejemplo
{
  "links": [
    { "longUrl": "https://example.com/landing?utm_source=sms&c=1" },
    { "longUrl": "https://example.com/landing?utm_source=sms&c=2", "code": "promo-octubre" },
    { "longUrl": "https://example.com/landing?utm_source=sms&c=3", "expiresAt": "2026-12-31T23:59:59-05:00" }
  ]
}

Respuestas satisfactorias

200

El lote se atendió: los totales y un resultado por enlace, en el orden del cuerpo. Con Idempotency-Key, la cabecera Idempotency-Status dice replayed si esta petición no creó nada nuevo y devolvió lo de antes, y created si creó algo. Cuando la llave devuelve enlaces de antes, los rechazos del lote entero (402, 409, 429, 5xx) no se contestan como error: van en cada enlace pendiente, con su código, y los replayed llegan igual.

Ejemplo de respuesta
{
  "success": true,
  "data": {
    "created": 2,
    "replayed": 0,
    "failed": 1,
    "items": [
      {
        "index": 0,
        "status": "created",
        "link": {
          "domain": "h0b.co",
          "code": "a1b2c3d",
          "shortUrl": "https://h0b.co/a1b2c3d",
          "longUrl": "https://example.com/landing?utm_source=sms&c=1",
          "isAlias": false,
          "status": "active",
          "domainStatus": "active",
          "expiresAt": null,
          "createdAt": "2026-09-30T15:04:05Z",
          "clicks": 0,
          "uniqueClicks": 0,
          "firstClickAt": null,
          "lastClickAt": null
        }
      },
      {
        "index": 1,
        "status": "failed",
        "error": { "code": "ALIAS_TAKEN", "message": "Ese código ya se usó en el dominio y no se puede volver a usar." }
      },
      {
        "index": 2,
        "status": "created",
        "link": {
          "domain": "h0b.co",
          "code": "k9x8w7v",
          "shortUrl": "https://h0b.co/k9x8w7v",
          "longUrl": "https://example.com/landing?utm_source=sms&c=3",
          "isAlias": false,
          "status": "active",
          "domainStatus": "active",
          "expiresAt": "2027-01-01T04:59:59Z",
          "createdAt": "2026-09-30T15:04:05Z",
          "clicks": 0,
          "uniqueClicks": 0,
          "firstClickAt": null,
          "lastClickAt": null
        }
      }
    ]
  },
  "meta": {
    "requestId": "8f0c0e2a4b1d4c8f",
    "clientIp": "203.0.113.45",
    "timestamp": "2026-09-23T18:30:00+00:00",
    "responseTimeMs": 7.2
  }
}

Los valores del ejemplo son ilustrativos.

Campos de la respuesta

NombreTipoDescripción y reglas
successSiempre presenteboolean

dataSiempre presenteobject

Lo que pasó con el lote: los totales y un resultado por enlace, en el mismo orden del cuerpo.

data.createdSiempre presenteinteger

Enlaces creados (y cobrados) en esta petición.

data.replayedSiempre presenteinteger

Enlaces que este lote ya había creado con la misma Idempotency-Key: se devuelven sin crearlos ni cobrarlos otra vez.

data.failedSiempre presenteinteger

Enlaces que no se crearon, cada uno con su error.

data.itemsSiempre presentearray<object>

Un resultado por enlace del cuerpo, en su orden.

data.items[].indexSiempre presenteinteger

Posición del enlace en links, empezando en 0.

data.items[].statusSiempre presentestring

created: se creó y se cobró ahora. replayed: ya lo había creado este lote con la misma llave. failed: no se creó; ver error.

Valores permitidos:created replayed failed

data.items[].linkobject

El enlace, con created y replayed.

data.items[].link.domainSiempre presentestring

Dominio del enlace, en minúsculas. Si el dominio ya no es de tu organización (domainStatus: released), es el nombre que tenía.

data.items[].link.codeSiempre presentestring

Código del enlace, en minúsculas: 7 caracteres 0-9a-z cuando lo genera el servicio, o el alias que se eligió al crearlo. Único por dominio.

data.items[].link.shortUrlSiempre presentestring

La URL corta completa: siempre https://{domain}/{code}.

Formato:uri

data.items[].link.longUrlSiempre presentestring

URL de destino.

Formato:uri

data.items[].link.isAliasSiempre presenteboolean

true cuando el código lo eligió quien creó el enlace (campo code al crearlo); false cuando lo generó el servicio.

data.items[].link.statusSiempre presentestring

active, disabled o blocked (y deleted, solo en el caso de abajo). Vencido no es un estado: un enlace active con expiresAt en el pasado ya no redirige, pero conserva active. blocked es un enlace que Hablame bloqueó por seguridad: no redirige y no se puede editar, eliminar ni reactivar desde la API; si crees que es un error, escríbenos. Los enlaces eliminados no aparecen en las respuestas, salvo en la repetición de un alta con Idempotency-Key cuyo enlace se eliminó después: llega tal como está, con deleted.

Valores permitidos:active disabled blocked deleted

data.items[].link.domainStatusstring

Si el dominio deja abrir el enlace, aparte de su propio status. active: lo deja. suspended: el dominio está suspendido; released: el dominio ya no es de tu organización; pending: es un dominio propio que todavía no está verificado. Con esos tres el enlace no redirige (el visitante recibe 404) aunque su status sea active. Llega en la lista, en la consulta de un enlace y al crearlo, editarlo o restaurarlo; si no llega, trátalo como antes de que existiera. Pueden aparecer valores nuevos: uno que no conozcas no significa que el enlace no abra.

Valores permitidos:active pending suspended released

data.items[].link.expiresAtSiempre presentestring

Vencimiento en UTC, o null si no vence.

Puede llegar en null.

Formato:date-time

data.items[].link.createdAtSiempre presentestring

Fecha de creación, en UTC.

Formato:date-time

data.items[].link.clicksSiempre presenteinteger

Visitas de toda la vida del enlace. Las consolida un proceso en segundo plano: una visita puede tardar de segundos a pocos minutos en reflejarse. No cuenta bots ni las vistas previas que generan las redes sociales y los chats.

data.items[].link.uniqueClicksSiempre presenteinteger

Suma de las visitas únicas de cada día: la primera visita del día desde cada navegador. No son personas distintas en toda la vida del enlace.

data.items[].link.firstClickAtSiempre presentestring

Primera visita, en UTC. null si nunca ha recibido una.

Puede llegar en null.

Formato:date-time

data.items[].link.lastClickAtSiempre presentestring

Última visita, en UTC. null si nunca ha recibido una.

Puede llegar en null.

Formato:date-time

data.items[].errorobject

Por qué no se creó, con failed.

data.items[].error.codeSiempre presentestring

El código del catálogo de errores: el mismo que devolvería el alta de ese enlace sola (URL_INVALID, ALIAS_TAKEN, DOMAIN_NOT_VERIFIED…).

data.items[].error.messageSiempre presentestring

El detalle, en español.

metaSiempre presenteobject

Metadatos de la respuesta.

meta.requestIdSiempre presentestring

Identificador único de la solicitud (16 caracteres hexadecimales). Es el mismo de la cabecera X-Request-Id. Cítalo al contactar soporte.

Ejemplo:b9b1704baffab211

meta.clientIpSiempre presentestring

IP con la que la API vio tu solicitud. Útil si tu API key solo admite ciertas redes.

Ejemplo:203.0.113.45

meta.timestampSiempre presentestring

Fecha y hora en que se construyó la respuesta (ISO 8601 con offset).

Formato:date-timeEjemplo:2026-06-04T19:43:59+00:00

meta.responseTimeMsSiempre presentenumber

Tiempo (ms) que tomó procesar la solicitud en el servidor.

Ejemplo:4.24

Códigos de error

Todas las respuestas de error tienen la misma forma: success en false y un objeto error cuyo code dice qué pasó. Aquí están agrupadas por tipo, con los códigos que este endpoint puede devolver.

Forma de una respuesta de error
NombreTipoDescripción y reglas
successSiempre presenteboolean

Valor fijo:false

metaSiempre presenteobject

Metadatos de la respuesta.

meta.requestIdSiempre presentestring

Identificador único de la solicitud (16 caracteres hexadecimales). Es el mismo de la cabecera X-Request-Id. Cítalo al contactar soporte.

Ejemplo:b9b1704baffab211

meta.clientIpSiempre presentestring

IP con la que la API vio tu solicitud. Útil si tu API key solo admite ciertas redes.

Ejemplo:203.0.113.45

meta.timestampSiempre presentestring

Fecha y hora en que se construyó la respuesta (ISO 8601 con offset).

Formato:date-timeEjemplo:2026-06-04T19:43:59+00:00

meta.responseTimeMsSiempre presentenumber

Tiempo (ms) que tomó procesar la solicitud en el servidor.

Ejemplo:4.24

errorSiempre presenteobject

error.codeSiempre presentestring

Identificador estable en UPPER_SNAKE_CASE. Los clientes ramifican sobre este valor.

Ejemplo:AUTH_REQUIRED

error.legacyCodeinteger

Código numérico legacy v5, solo cuando hay puente. Se omite en los códigos nacidos en v6.

Ejemplo:40002

error.typeSiempre presentestring

URL a la documentación del error.

Formato:uriEjemplo:https://developers.hablame.co/docs/v6/errors/auth-required

error.messageSiempre presentestring

Texto legible en inglés. Es para el log del desarrollador, no para el usuario final: no lo parsees.

error.detailsSiempre presentearray<object>

Contexto estructurado (validaciones, recurso afectado). Arreglo vacío cuando no hay contexto extra.

Validación

  • 400El cuerpo no se puede leer, no trae enlaces, trae más de 1.000 (URLSHORTENER_BATCH_TOO_LARGE) o un campo que no existe, o la Idempotency-Key tiene otra forma. No se creó nada.
  • 422La Idempotency-Key ya creó enlaces con otro cuerpo. Usa una llave nueva para un lote nuevo.
  • 400VALIDATION_INVALID_PARAMETER

    Un parámetro o un campo del cuerpo no es válido.

  • 400URLSHORTENER_BATCH_TOO_LARGE

    El lote trae más enlaces de los que admite una petición.

  • 400IDEMPOTENCY_KEY_INVALID

    La llave de idempotencia no cumple el formato.

  • 422IDEMPOTENCY_KEY_REUSED

    La llave ya existe, pero con un cuerpo distinto.

  • 400URL_INVALID

    La URL de destino está mal formada.

    Mensaje que devuelve la API: The destination URL is not valid. Provide an absolute `http`/`https` URL with a host.

  • 400URL_SCHEME_NOT_ALLOWED

    Solo se admiten destinos http y https.

    Mensaje que devuelve la API: Only `http` and `https` destination URLs are allowed.

  • 400URL_TOO_LONG

    La URL de destino supera el largo máximo.

    Mensaje que devuelve la API: The destination URL exceeds the maximum allowed length.

  • 400URL_BLOCKED_HOST

    El host de destino es privado o reservado.

    Mensaje que devuelve la API: The destination host is not allowed (private, reserved, or loopback addresses are blocked).

  • 400ALIAS_INVALID

    El alias no cumple el formato permitido.

    Mensaje que devuelve la API: The alias must be 3-32 characters of lowercase letters, digits, hyphen or underscore.

  • 400EXPIRES_AT_INVALID

    expiresAt debe ser una fecha ISO futura.

    Mensaje que devuelve la API: `expiresAt` must be a future ISO-8601 date-time.

  • 400DOMAIN_NOT_ALLOWED

    El dominio elegido no está disponible para tu cuenta.

    Mensaje que devuelve la API: The requested domain is not available for this account.

Autenticación

  • 401Credenciales inválidas o faltantes.
  • 401AUTH_REQUIRED

    No se envió un token Bearer y cada llamada lo requiere.

    Mensaje que devuelve la API: Authentication is required. Send your API key as `Authorization: Bearer <token>`.

  • 401AUTH_INVALID_KEY

    La clave de API no se reconoce: formato equivocado, revocada o vencida.

    Mensaje que devuelve la API: The provided API key is invalid or has expired. Please verify the value sent in the `Authorization: Bearer` header.

Permisos

  • 403La API key no tiene habilitado el acortador (o está apagado para tu organización), la key no se puede usar desde la red de la petición, o la organización no está activa.
  • 403AUTH_SERVICE_NOT_ALLOWED

    La clave no tiene permitido el servicio detrás de este endpoint.

    Mensaje que devuelve la API: This API key is not authorized to use the requested service.

  • 403AUTH_IP_NOT_ALLOWED

    La clave solo funciona desde ciertas redes y tu IP no está entre ellas.

    Mensaje que devuelve la API: This API key cannot be used from this IP address. The address we saw is in `meta.clientIp`: call from one of the networks allowed for the key, or use a key that allows this one.

  • 403ACCOUNT_NOT_ACTIVE

    Tu organización está suspendida o cerrada.

    Mensaje que devuelve la API: The organization associated with this API key is not active. Please contact support.

  • 403ACCOUNT_CONFIG_NOT_FOUND

    No pudimos cargar la organización de tu clave.

    Mensaje que devuelve la API: Organization configuration could not be loaded. Please contact support.

Límites de uso

  • 429Se excedió el límite de solicitudes, el de enlaces por minuto de los lotes, o (con plan) el cupo diario de altas. El lote entero se rechaza sin crear nada; Retry-After dice cuánto esperar.
  • 429URLSHORTENER_LINK_QUOTA_EXCEEDED

    Tu organización llegó al cupo diario de enlaces de su plan.

  • 429RATE_TPS_EXCEEDED

    Excediste el cupo de tu organización para este endpoint.

    Mensaje que devuelve la API: You have exceeded the allowed request rate for this endpoint. Please wait before retrying.

Recurso inexistente

Este endpoint no declara respuestas de este tipo.

Errores internos y disponibilidad

  • 500Error interno. Reintenta con la misma Idempotency-Key: lo que ya se hubiera creado vuelve como replayed.
  • 503No se pudo comprobar el saldo en este momento. No se creó nada; puedes reintentar (mejor con Idempotency-Key).

Otras respuestas

  • 402Tu organización no tiene saldo para todos los enlaces válidos del lote (o no tiene cuenta de facturación activa), o crearlos superaría un tope de gasto que ella misma configuró. No se creó ninguno.
  • 409El acortador no tiene precio para la moneda de tu organización, o tu organización todavía no tiene moneda. No se arregla reintentando.

Ver en el catálogo de errores

Consideraciones especiales

  • Admite la cabecera Idempotency-Key: si una petición se corta y la repites con la misma llave, no se duplica. Los detalles están en la guía de idempotencia.

Reintenta sin duplicar con Idempotency-Key. La llave es del lote: si una petición se corta y la repites con la misma llave y el mismo cuerpo, los enlaces que ya se crearon vuelven como replayed (los mismos, sin crear otros ni cobrarlos dos veces, aunque la primera todavía se esté procesando) y los que fallaron se vuelven a intentar. Lo ya creado nunca se esconde: si lo que faltaba choca con el saldo, un límite o un fallo nuestro, la respuesta sigue siendo 200 con los replayed, y cada enlace pendiente sale failed con el código de ese rechazo. Con la llave y otro cuerpo, 422 IDEMPOTENCY_KEY_REUSED. Sin llave, cada reintento crea y cobra otro lote.

El límite cuenta enlaces, no peticiones. Además del cupo de solicitudes, que comparte con el alta de un enlace, tu organización puede crear en lote hasta 2.100 enlaces por minuto (35 por segundo, el mismo ritmo del alta de uno). Caben dos lotes de 1.000 seguidos; un lote que no cabe se rechaza entero con 429 RATE_TPS_EXCEEDED y Retry-After, sin crear ni cobrar nada. El alta de un enlace no gasta este límite.

Una key con el acortador también crea y borra

El servicio urlshortener da todo el acortador: con él, la API key lista y consulta enlaces, y también los crea (cada alta se cobra), los edita, los borra y los restaura. No hay keys de solo lectura. Solo el propietario de tu organización crea las keys: si una no debe crear enlaces, no le des este servicio. Sin él, la key recibe 403 AUTH_SERVICE_NOT_ALLOWED.

Documentación relacionada

¿Necesitas ayuda?

¿Te sirvió esta página?

Ver todos los canales de soporte

Pide ayuda sobre esto: este endpoint

Armamos un mensaje con los datos que ayudan a revisar tu caso. Léelo, complétalo y envíalo tú: no se manda nada solo.

El mensaje nunca lleva tu clave de API ni contraseñas.