{
  "info": {
    "_postman_id": "373a3db5-048a-48c2-a616-fc72619d3237",
    "name": "Hablame API v6",
    "description": {
      "content": "Colección oficial de la API v6 de Hablame, generada desde el mismo catálogo que la documentación.\n\n**Cómo usarla**\n1. Importa también el entorno «Hablame · Producción» y elígelo arriba a la derecha.\n2. Pega tu clave de API en la variable `apiKey` del entorno (es secreta y viene vacía).\n3. Envía «Ping» para comprobar que la clave funciona.\n\n**Es segura por defecto.** Toda petición que crea, modifica, borra, envía, llama o cobra se salta sola, también en el Runner, salvo que el entorno tenga `confirmoEfectos` en `si`. La carpeta «Flujos» se protege además con `confirmoFlujos`. Todo lo que envíes es real y puede cobrarse.",
      "type": "text/markdown"
    },
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "version": "6.0.0"
  },
  "item": [
    {
      "name": "Utilidades",
      "description": {
        "content": "Endpoints de diagnóstico, disponibles para cualquier API key. Sirven para verificar que tus credenciales quedaron bien conectadas.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Ping",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/utilities/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "utilities",
                "ping"
              ]
            },
            "description": {
              "content": "Confirma que tu API key es válida y muestra lo que la API sabe de quien llama.\n\nDevuelve el estado del llamante: confirma que la API key es válida y muestra los datos clave que la API conoce de quien llama.\n\nÚsalo como prueba de humo después de configurar tus credenciales. Un `200` significa que tu integración está lista para llamar al resto de los endpoints.\n\n- Que la key es válida (`pong: true`).\n- La versión de la API que atendió el llamado.\n- La IP pública desde la que se origina el llamado.\n\nLos datos de tu organización **no** viajan en esta respuesta: los consultas en `/api/v6/account/organization`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 20 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/utilities/ping",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "utilities",
                    "ping"
                  ]
                },
                "description": {
                  "content": "Confirma que tu API key es válida y muestra lo que la API sabe de quien llama.\n\nDevuelve el estado del llamante: confirma que la API key es válida y muestra los datos clave que la API conoce de quien llama.\n\nÚsalo como prueba de humo después de configurar tus credenciales. Un `200` significa que tu integración está lista para llamar al resto de los endpoints.\n\n- Que la key es válida (`pong: true`).\n- La versión de la API que atendió el llamado.\n- La IP pública desde la que se origina el llamado.\n\nLos datos de tu organización **no** viajan en esta respuesta: los consultas en `/api/v6/account/organization`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 20 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"pong\": true,\n    \"apiVersion\": \"v6\",\n    \"client\": { \"ip\": \"203.0.113.45\" }\n  },\n  \"meta\": {\n    \"requestId\": \"b9b1704baffab211\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-06-04T19:43:59+00:00\",\n    \"responseTimeMs\": 4.24\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Herramientas",
      "description": {
        "content": "Catálogos de propósito general que puedes consumir desde tu aplicación. Disponibles para cualquier API key.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Listar países",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/tools/countries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "tools",
                "countries"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "es",
                  "description": "Código ISO-639-1. Cuando se especifica, cada país devuelve `name` plano en ese idioma en vez del mapa completo `names`.",
                  "disabled": true
                },
                {
                  "key": "callingCode",
                  "value": "57",
                  "description": "Filtra países por código telefónico E.164 (por ejemplo `57` para Colombia). Acepta un `+` inicial opcional.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "col",
                  "description": "Búsqueda por subcadena, sin distinguir mayúsculas, sobre el nombre del país en el idioma solicitado. Mínimo 2 caracteres.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Catálogo completo de países con código ISO, código telefónico y MCC.\n\nDevuelve el catálogo completo de países que la API conoce, con su código ISO, código telefónico, MCC móvil y nombres en inglés y español. Úsalo para renderizar selectores de país, autodetectar el país a partir de un prefijo o resolver MCCs.\n\nEl catálogo cambia raras veces. La respuesta incluye `Cache-Control: public, max-age=3600` para que tu cliente y cualquier CDN intermedio amortigüen el tráfico.\n\nLos nombres se ofrecen solo en inglés (`en`) y español (`es`). Cualquier otro valor de `lang` cae a inglés.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/tools/countries",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "tools",
                    "countries"
                  ],
                  "query": [
                    {
                      "key": "lang",
                      "value": "es",
                      "description": "Código ISO-639-1. Cuando se especifica, cada país devuelve `name` plano en ese idioma en vez del mapa completo `names`.",
                      "disabled": true
                    },
                    {
                      "key": "callingCode",
                      "value": "57",
                      "description": "Filtra países por código telefónico E.164 (por ejemplo `57` para Colombia). Acepta un `+` inicial opcional.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "col",
                      "description": "Búsqueda por subcadena, sin distinguir mayúsculas, sobre el nombre del país en el idioma solicitado. Mínimo 2 caracteres.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Catálogo completo de países con código ISO, código telefónico y MCC.\n\nDevuelve el catálogo completo de países que la API conoce, con su código ISO, código telefónico, MCC móvil y nombres en inglés y español. Úsalo para renderizar selectores de país, autodetectar el país a partir de un prefijo o resolver MCCs.\n\nEl catálogo cambia raras veces. La respuesta incluye `Cache-Control: public, max-age=3600` para que tu cliente y cualquier CDN intermedio amortigüen el tráfico.\n\nLos nombres se ofrecen solo en inglés (`en`) y español (`es`). Cualquier otro valor de `lang` cae a inglés.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": [\n    {\n      \"code\": \"CO\",\n      \"code3\": \"COL\",\n      \"callingCode\": \"57\",\n      \"mcc\": \"732\",\n      \"flag\": \"https://flagcdn.com/co.svg\",\n      \"names\": { \"en\": \"Colombia\", \"es\": \"Colombia\" }\n    },\n    {\n      \"code\": \"MX\",\n      \"code3\": \"MEX\",\n      \"callingCode\": \"52\",\n      \"mcc\": \"334\",\n      \"flag\": \"https://flagcdn.com/mx.svg\",\n      \"names\": { \"en\": \"Mexico\", \"es\": \"México\" }\n    }\n  ],\n  \"meta\": {\n    \"requestId\": \"b9b1704baffab211\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-05-23T02:00:00+00:00\",\n    \"responseTimeMs\": 4.12\n  }\n}"
            }
          ]
        },
        {
          "name": "Obtener un país",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/tools/countries/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "tools",
                "countries",
                ":code"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "es",
                  "description": "Código ISO-639-1. Devuelve `name` plano en ese idioma en vez del mapa completo `names`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "CO",
                  "description": "Código ISO 3166-1 alpha-2 del país (`CO`, `MX`, `US`...). No distingue mayúsculas."
                }
              ]
            },
            "description": {
              "content": "Datos de un país por su código ISO 3166-1 alpha-2.\n\nDevuelve los datos de un país por su código ISO 3166-1 alpha-2 (2 letras, sin distinguir mayúsculas).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/tools/countries/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "tools",
                    "countries",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "lang",
                      "value": "es",
                      "description": "Código ISO-639-1. Devuelve `name` plano en ese idioma en vez del mapa completo `names`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "CO",
                      "description": "Código ISO 3166-1 alpha-2 del país (`CO`, `MX`, `US`...). No distingue mayúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Datos de un país por su código ISO 3166-1 alpha-2.\n\nDevuelve los datos de un país por su código ISO 3166-1 alpha-2 (2 letras, sin distinguir mayúsculas).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"code\": \"CO\",\n    \"code3\": \"COL\",\n    \"callingCode\": \"57\",\n    \"mcc\": \"732\",\n    \"flag\": \"https://flagcdn.com/co.svg\",\n    \"name\": \"Colombia\"\n  },\n  \"meta\": {\n    \"requestId\": \"b9b1704baffab211\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-05-23T02:00:00+00:00\",\n    \"responseTimeMs\": 2.1\n  }\n}"
            },
            {
              "name": "404 Not Found · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/tools/countries/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "tools",
                    "countries",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "lang",
                      "value": "es",
                      "description": "Código ISO-639-1. Devuelve `name` plano en ese idioma en vez del mapa completo `names`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "CO",
                      "description": "Código ISO 3166-1 alpha-2 del país (`CO`, `MX`, `US`...). No distingue mayúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Datos de un país por su código ISO 3166-1 alpha-2.\n\nDevuelve los datos de un país por su código ISO 3166-1 alpha-2 (2 letras, sin distinguir mayúsculas).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: Cualquier API key válida.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "Not Found",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"COUNTRY_NOT_FOUND\",\n    \"type\": \"https://developers.hablame.co/docs/v6/errors/country-not-found\",\n    \"message\": \"No country was found with the supplied code. Use the ISO 3166-1 alpha-2 form (e.g. `CO`, `US`).\",\n    \"details\": []\n  },\n  \"meta\": {\n    \"requestId\": \"b9b1704baffab211\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-05-23T02:00:00+00:00\",\n    \"responseTimeMs\": 1.4\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Acortador de URLs",
      "description": {
        "content": "Crea y administra enlaces cortos en tus dominios y consulta su analítica de clics.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Crear un enlace corto",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «Crear un enlace corto» porque tiene efectos (crea, modifica, borra, envía, llama o cobra). Pon confirmoEfectos en si en el entorno para enviarla.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([201]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Llave de idempotencia opcional (1 a 255 caracteres de `A-Za-z0-9_-`), única por organización. Si repites el alta con la misma llave y el mismo cuerpo, recibes el enlace que ya se creó, sin crear otro ni cobrarlo otra vez. Ver la [guía de idempotencia](https://developers.hablame.co/docs/idempotency)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"longUrl\": \"https://example.com/landing?utm_source=sms\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": {
              "content": "Publica un enlace corto con código generado o alias propio. Se cobra al crearlo, salvo que tu plan lo incluya.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCrea un enlace corto en uno de los dominios disponibles para tu organización. Si no envías `code`, el servicio genera uno de 7 caracteres (`0-9a-z`); si lo envías, queda como alias (`isAlias: true`). Los códigos son únicos **por dominio**, y el de un enlace eliminado no se puede volver a usar para otro enlace: si era tuyo, puedes [restaurarlo](https://developers.hablame.co/docs/reference/urlshortener/restore) durante 90 días.\n\nCada enlace creado se cobra al precio vigente de tu organización (consúltalo con [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/docs/reference/urlshortener/pricing)). Si tu organización tiene un plan mensual del acortador vigente, crear enlaces no se cobra, y entonces rige un **cupo diario de altas** (100.000 por día de Colombia, salvo que tengas otro pactado): al llegar, la API responde `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` hasta la medianoche. Sin plan no hay cupo diario: sin saldo disponible la API responde `402` y el enlace no se crea.\n\n> **Reintenta sin duplicar con `Idempotency-Key`.** Si un alta se corta (un timeout, un `5xx`) y la repites con la misma llave y el mismo cuerpo, recibes el mismo enlace (`201` con `Idempotency-Status: replayed`) sin crear otro ni cobrarlo dos veces, aunque la primera todavía se esté procesando. La misma llave con otro cuerpo responde `422 IDEMPOTENCY_KEY_REUSED`. En este endpoint la llave queda asociada al enlace para siempre. Sin llave, cada reintento crea y cobra otro enlace.\n\n> El cuerpo se valida **antes** de comprobar el saldo: una URL, un alias, un dominio o un vencimiento inválidos responden su error sin tocar tu saldo.\n\n> **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`.\n\n¿Muchos enlaces a la vez, por ejemplo para una campaña? Usa el [alta en lote](https://developers.hablame.co/docs/reference/urlshortener/batch): hasta 1.000 por petición, con las mismas reglas y un solo cobro por lo que se creó.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- La URL de destino admite hasta 2048 caracteres; el cuerpo, hasta 64 KiB.\n- Se rechazan destinos en `localhost`, `*.local`, `*.internal` o en IP privadas o reservadas; destinos que ya son un enlace corto (nuestros dominios o acortadores como bit.ly, tinyurl.com o t.co); destinos con usuario o clave antes del dominio (`usuario@`); dominios con tildes, eñes u otros alfabetos (también en `xn--`), y URL con caracteres invisibles.\n- Con plan: hasta 100.000 altas por día de Colombia (cupo diario).\n- Admite `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "201 Created · Ejemplo",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Llave de idempotencia opcional (1 a 255 caracteres de `A-Za-z0-9_-`), única por organización. Si repites el alta con la misma llave y el mismo cuerpo, recibes el enlace que ya se creó, sin crear otro ni cobrarlo otra vez. Ver la [guía de idempotencia](https://developers.hablame.co/docs/idempotency)."
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"longUrl\": \"https://example.com/landing?utm_source=sms\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": {
                  "content": "Publica un enlace corto con código generado o alias propio. Se cobra al crearlo, salvo que tu plan lo incluya.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCrea un enlace corto en uno de los dominios disponibles para tu organización. Si no envías `code`, el servicio genera uno de 7 caracteres (`0-9a-z`); si lo envías, queda como alias (`isAlias: true`). Los códigos son únicos **por dominio**, y el de un enlace eliminado no se puede volver a usar para otro enlace: si era tuyo, puedes [restaurarlo](https://developers.hablame.co/docs/reference/urlshortener/restore) durante 90 días.\n\nCada enlace creado se cobra al precio vigente de tu organización (consúltalo con [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/docs/reference/urlshortener/pricing)). Si tu organización tiene un plan mensual del acortador vigente, crear enlaces no se cobra, y entonces rige un **cupo diario de altas** (100.000 por día de Colombia, salvo que tengas otro pactado): al llegar, la API responde `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` hasta la medianoche. Sin plan no hay cupo diario: sin saldo disponible la API responde `402` y el enlace no se crea.\n\n> **Reintenta sin duplicar con `Idempotency-Key`.** Si un alta se corta (un timeout, un `5xx`) y la repites con la misma llave y el mismo cuerpo, recibes el mismo enlace (`201` con `Idempotency-Status: replayed`) sin crear otro ni cobrarlo dos veces, aunque la primera todavía se esté procesando. La misma llave con otro cuerpo responde `422 IDEMPOTENCY_KEY_REUSED`. En este endpoint la llave queda asociada al enlace para siempre. Sin llave, cada reintento crea y cobra otro enlace.\n\n> El cuerpo se valida **antes** de comprobar el saldo: una URL, un alias, un dominio o un vencimiento inválidos responden su error sin tocar tu saldo.\n\n> **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`.\n\n¿Muchos enlaces a la vez, por ejemplo para una campaña? Usa el [alta en lote](https://developers.hablame.co/docs/reference/urlshortener/batch): hasta 1.000 por petición, con las mismas reglas y un solo cobro por lo que se creó.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- La URL de destino admite hasta 2048 caracteres; el cuerpo, hasta 64 KiB.\n- Se rechazan destinos en `localhost`, `*.local`, `*.internal` o en IP privadas o reservadas; destinos que ya son un enlace corto (nuestros dominios o acortadores como bit.ly, tinyurl.com o t.co); destinos con usuario o clave antes del dominio (`usuario@`); dominios con tildes, eñes u otros alfabetos (también en `xn--`), y URL con caracteres invisibles.\n- Con plan: hasta 100.000 altas por día de Colombia (cupo diario).\n- Admite `Idempotency-Key`.",
                  "type": "text/markdown"
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Crear enlaces cortos en lote",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «Crear enlaces cortos en lote» porque tiene efectos (crea, modifica, borra, envía, llama o cobra). Pon confirmoEfectos en si en el entorno para enviarla.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "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](https://developers.hablame.co/docs/idempotency)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/batch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                "batch"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"links\": [\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=1\" },\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=2\" },\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=3\" }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": {
              "content": "Hasta 1.000 enlaces en una petición, con un resultado por enlace y un solo cobro por lo que se creó.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCrea hasta **1.000 enlaces** en una petición. Cada enlace del lote lleva los mismos cuatro campos que el [alta de un enlace](https://developers.hablame.co/docs/reference/urlshortener/create) y pasa exactamente por las mismas comprobaciones: el destino, el alias, el dominio y el vencimiento.\n\n**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`).\n\n**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.\n\n> **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.\n\n> **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.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- Las 35 solicitudes por segundo son las mismas del alta de un enlace: las dos rutas gastan un solo cupo.\n- Además, hasta 2.100 enlaces por minuto por organización entre todos sus lotes. Un lote que no cabe se rechaza entero.\n- Hasta 1.000 enlaces por petición; el cuerpo, hasta 3 MiB; cada destino, hasta 2048 caracteres.\n- Con plan: los enlaces del lote cuentan en el cupo diario de altas (100.000 por día de Colombia).\n- Admite `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "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](https://developers.hablame.co/docs/idempotency)."
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/batch",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    "batch"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"links\": [\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=1\" },\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=2\" },\n    { \"longUrl\": \"https://example.com/landing?utm_source=sms&c=3\" }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": {
                  "content": "Hasta 1.000 enlaces en una petición, con un resultado por enlace y un solo cobro por lo que se creó.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCrea hasta **1.000 enlaces** en una petición. Cada enlace del lote lleva los mismos cuatro campos que el [alta de un enlace](https://developers.hablame.co/docs/reference/urlshortener/create) y pasa exactamente por las mismas comprobaciones: el destino, el alias, el dominio y el vencimiento.\n\n**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`).\n\n**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.\n\n> **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.\n\n> **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.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- Las 35 solicitudes por segundo son las mismas del alta de un enlace: las dos rutas gastan un solo cupo.\n- Además, hasta 2.100 enlaces por minuto por organización entre todos sus lotes. Un lote que no cabe se rechaza entero.\n- Hasta 1.000 enlaces por petición; el cuerpo, hasta 3 MiB; cada destino, hasta 2048 caracteres.\n- Con plan: los enlaces del lote cuentan en el cupo diario de altas (100.000 por día de Colombia).\n- Admite `Idempotency-Key`.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"created\": 2,\n    \"replayed\": 0,\n    \"failed\": 1,\n    \"items\": [\n      {\n        \"index\": 0,\n        \"status\": \"created\",\n        \"link\": {\n          \"domain\": \"h0b.co\",\n          \"code\": \"a1b2c3d\",\n          \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n          \"longUrl\": \"https://example.com/landing?utm_source=sms&c=1\",\n          \"isAlias\": false,\n          \"status\": \"active\",\n          \"domainStatus\": \"active\",\n          \"expiresAt\": null,\n          \"createdAt\": \"2026-09-30T15:04:05Z\",\n          \"clicks\": 0,\n          \"uniqueClicks\": 0,\n          \"firstClickAt\": null,\n          \"lastClickAt\": null\n        }\n      },\n      {\n        \"index\": 1,\n        \"status\": \"failed\",\n        \"error\": { \"code\": \"ALIAS_TAKEN\", \"message\": \"Ese código ya se usó en el dominio y no se puede volver a usar.\" }\n      },\n      {\n        \"index\": 2,\n        \"status\": \"created\",\n        \"link\": {\n          \"domain\": \"h0b.co\",\n          \"code\": \"k9x8w7v\",\n          \"shortUrl\": \"https://h0b.co/k9x8w7v\",\n          \"longUrl\": \"https://example.com/landing?utm_source=sms&c=3\",\n          \"isAlias\": false,\n          \"status\": \"active\",\n          \"domainStatus\": \"active\",\n          \"expiresAt\": \"2027-01-01T04:59:59Z\",\n          \"createdAt\": \"2026-09-30T15:04:05Z\",\n          \"clicks\": 0,\n          \"uniqueClicks\": 0,\n          \"firstClickAt\": null,\n          \"lastClickAt\": null\n        }\n      }\n    ]\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Listar enlaces cortos",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "Número de página (empieza en 1). Llega hasta el enlace 10.000: la página tiene que empezar, como mucho, en el enlace 10.000. Con `cursor` se ignora.",
                  "disabled": true
                },
                {
                  "key": "perPage",
                  "value": "20",
                  "description": "Ítems por página (máximo 100).",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "El `nextCursor` de la respuesta anterior, tal cual. La página empieza justo después del último enlace de esa respuesta. Tiene que ir con el mismo `sort` y `order`; un cursor que no es de esta organización o de este orden responde `400`.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Busca en el código y en la URL de destino, por fragmento y sin distinguir mayúsculas ni tildes (mínimo 2 caracteres; con menos se ignora). Si pegas una URL corta completa (`h0b.co/a1b2c3d` o `https://h0b.co/a1b2c3d`), trae exactamente ese enlace, también si su dominio ya no es de tu organización (`domainStatus: released`).",
                  "disabled": true
                },
                {
                  "key": "domain",
                  "value": "",
                  "description": "Solo los enlaces de este dominio. Los de un dominio que ya no es de tu organización (`domainStatus: released`) no entran aunque se llamen igual: para encontrar uno, pega su URL corta en `search`.",
                  "disabled": true
                },
                {
                  "key": "state",
                  "value": "",
                  "description": "Estado tal como lo vive quien visita: `active` (activo y sin vencer), `expired` (activo con el vencimiento ya pasado: ya no redirige), `disabled` o `blocked` (bloqueado por Hablame).",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "description": "Estado guardado. `active` incluye los vencidos. Se conserva por compatibilidad; prefiere `state`.",
                  "disabled": true
                },
                {
                  "key": "kind",
                  "value": "",
                  "description": "Enlaces con alias elegido, o con código generado.",
                  "disabled": true
                },
                {
                  "key": "traffic",
                  "value": "",
                  "description": "`none`: los que nunca han recibido una visita. `some`: los que han recibido al menos una.",
                  "disabled": true
                },
                {
                  "key": "createdFrom",
                  "value": "2026-09-01",
                  "description": "Creados desde este día (`AAAA-MM-DD`, hora de Colombia), incluido.",
                  "disabled": true
                },
                {
                  "key": "createdTo",
                  "value": "2026-09-23",
                  "description": "Creados hasta este día (`AAAA-MM-DD`, hora de Colombia), incluido.",
                  "disabled": true
                },
                {
                  "key": "sort",
                  "value": "created",
                  "description": "Orden: por fecha de creación, por visitas de toda la vida, por última visita o por vencimiento. Con `lastClick` y `expires`, los enlaces sin ese dato van al final en los dos sentidos.",
                  "disabled": true
                },
                {
                  "key": "order",
                  "value": "desc",
                  "description": "Sentido del orden.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Tus enlaces paginados, con búsqueda, filtros, orden y sus contadores de visitas.\n\nLista los enlaces de tu organización (nunca los eliminados) con sus contadores ya consolidados. Por defecto, del más nuevo al más viejo. Todos los filtros son opcionales y se combinan entre sí.\n\n> `status` conserva su significado de siempre: `active` **incluye los vencidos**, porque vencer no cambia el estado guardado. Para separar los vigentes de los vencidos usa `state`.\n\n**Para recorrer muchos enlaces, pagina con `cursor`.** Cada respuesta trae `nextCursor` mientras haya más: pásalo en la siguiente petición, con los mismos filtros, `sort` y `order`, y la página empieza justo después del último enlace de la anterior. `page` sigue funcionando como siempre, pero solo hasta el enlace 10.000 (con `perPage=100`, hasta la página 100); más allá responde `400` y hay que seguir con `cursor`, que no tiene tope.\n\n**`total` es exacto hasta 10.000.** Si los enlaces que cumplen los filtros son más, `total` llega en `10000` y `totalIsExact` en `false`: «más de 10.000». `hasMore` dice siempre, sin error, si hay otra página.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 30 solicitudes por minuto.\n- Hasta 100 ítems por página; un valor mayor se recorta a 100.\n- `total` es exacto hasta 10.000; por encima, `totalIsExact` es `false`.\n- Con `page` se llega hasta el enlace 10.000; para seguir, `cursor`.\n- Una búsqueda o un orden demasiado costosos para una organización muy grande se detienen a los pocos segundos con `504`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "Número de página (empieza en 1). Llega hasta el enlace 10.000: la página tiene que empezar, como mucho, en el enlace 10.000. Con `cursor` se ignora.",
                      "disabled": true
                    },
                    {
                      "key": "perPage",
                      "value": "20",
                      "description": "Ítems por página (máximo 100).",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "El `nextCursor` de la respuesta anterior, tal cual. La página empieza justo después del último enlace de esa respuesta. Tiene que ir con el mismo `sort` y `order`; un cursor que no es de esta organización o de este orden responde `400`.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "Busca en el código y en la URL de destino, por fragmento y sin distinguir mayúsculas ni tildes (mínimo 2 caracteres; con menos se ignora). Si pegas una URL corta completa (`h0b.co/a1b2c3d` o `https://h0b.co/a1b2c3d`), trae exactamente ese enlace, también si su dominio ya no es de tu organización (`domainStatus: released`).",
                      "disabled": true
                    },
                    {
                      "key": "domain",
                      "value": "",
                      "description": "Solo los enlaces de este dominio. Los de un dominio que ya no es de tu organización (`domainStatus: released`) no entran aunque se llamen igual: para encontrar uno, pega su URL corta en `search`.",
                      "disabled": true
                    },
                    {
                      "key": "state",
                      "value": "",
                      "description": "Estado tal como lo vive quien visita: `active` (activo y sin vencer), `expired` (activo con el vencimiento ya pasado: ya no redirige), `disabled` o `blocked` (bloqueado por Hablame).",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "Estado guardado. `active` incluye los vencidos. Se conserva por compatibilidad; prefiere `state`.",
                      "disabled": true
                    },
                    {
                      "key": "kind",
                      "value": "",
                      "description": "Enlaces con alias elegido, o con código generado.",
                      "disabled": true
                    },
                    {
                      "key": "traffic",
                      "value": "",
                      "description": "`none`: los que nunca han recibido una visita. `some`: los que han recibido al menos una.",
                      "disabled": true
                    },
                    {
                      "key": "createdFrom",
                      "value": "2026-09-01",
                      "description": "Creados desde este día (`AAAA-MM-DD`, hora de Colombia), incluido.",
                      "disabled": true
                    },
                    {
                      "key": "createdTo",
                      "value": "2026-09-23",
                      "description": "Creados hasta este día (`AAAA-MM-DD`, hora de Colombia), incluido.",
                      "disabled": true
                    },
                    {
                      "key": "sort",
                      "value": "created",
                      "description": "Orden: por fecha de creación, por visitas de toda la vida, por última visita o por vencimiento. Con `lastClick` y `expires`, los enlaces sin ese dato van al final en los dos sentidos.",
                      "disabled": true
                    },
                    {
                      "key": "order",
                      "value": "desc",
                      "description": "Sentido del orden.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Tus enlaces paginados, con búsqueda, filtros, orden y sus contadores de visitas.\n\nLista los enlaces de tu organización (nunca los eliminados) con sus contadores ya consolidados. Por defecto, del más nuevo al más viejo. Todos los filtros son opcionales y se combinan entre sí.\n\n> `status` conserva su significado de siempre: `active` **incluye los vencidos**, porque vencer no cambia el estado guardado. Para separar los vigentes de los vencidos usa `state`.\n\n**Para recorrer muchos enlaces, pagina con `cursor`.** Cada respuesta trae `nextCursor` mientras haya más: pásalo en la siguiente petición, con los mismos filtros, `sort` y `order`, y la página empieza justo después del último enlace de la anterior. `page` sigue funcionando como siempre, pero solo hasta el enlace 10.000 (con `perPage=100`, hasta la página 100); más allá responde `400` y hay que seguir con `cursor`, que no tiene tope.\n\n**`total` es exacto hasta 10.000.** Si los enlaces que cumplen los filtros son más, `total` llega en `10000` y `totalIsExact` en `false`: «más de 10.000». `hasMore` dice siempre, sin error, si hay otra página.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 30 solicitudes por minuto.\n- Hasta 100 ítems por página; un valor mayor se recorta a 100.\n- `total` es exacto hasta 10.000; por encima, `totalIsExact` es `false`.\n- Con `page` se llega hasta el enlace 10.000; para seguir, `cursor`.\n- Una búsqueda o un orden demasiado costosos para una organización muy grande se detienen a los pocos segundos con `504`.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"items\": [\n      {\n        \"domain\": \"h0b.co\",\n        \"code\": \"a1b2c3d\",\n        \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n        \"longUrl\": \"https://example.com/landing\",\n        \"isAlias\": false,\n        \"status\": \"active\",\n        \"domainStatus\": \"active\",\n        \"expiresAt\": null,\n        \"createdAt\": \"2026-09-20T15:04:05Z\",\n        \"clicks\": 128,\n        \"uniqueClicks\": 96,\n        \"firstClickAt\": \"2026-09-20T15:31:40Z\",\n        \"lastClickAt\": \"2026-09-23T18:22:10Z\"\n      }\n    ],\n    \"page\": 1,\n    \"perPage\": 20,\n    \"total\": 1,\n    \"totalIsExact\": true,\n    \"hasMore\": false,\n    \"nextCursor\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Listar dominios disponibles",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/domains",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "domains"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "",
                  "description": "`unverified` agrega tus dominios propios que todavía no están verificados, para verlos; no sirven para crear enlaces.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Dominios donde tu organización puede publicar enlaces, con el predeterminado primero.\n\nLista los dominios activos donde tu organización puede publicar enlaces: los de la plataforma más los propios ya verificados, con el predeterminado primero y el resto en orden alfabético. Un dominio propio que todavía no está verificado no sirve para crear enlaces y no aparece, salvo que lo pidas con `include=unverified` (llega con `verified: false`). No hay un endpoint para registrar o verificar dominios propios.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 30 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/domains",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "domains"
                  ],
                  "query": [
                    {
                      "key": "include",
                      "value": "",
                      "description": "`unverified` agrega tus dominios propios que todavía no están verificados, para verlos; no sirven para crear enlaces.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Dominios donde tu organización puede publicar enlaces, con el predeterminado primero.\n\nLista los dominios activos donde tu organización puede publicar enlaces: los de la plataforma más los propios ya verificados, con el predeterminado primero y el resto en orden alfabético. Un dominio propio que todavía no está verificado no sirve para crear enlaces y no aparece, salvo que lo pidas con `include=unverified` (llega con `verified: false`). No hay un endpoint para registrar o verificar dominios propios.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 30 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domains\": [\n      { \"name\": \"h0b.co\", \"isDefault\": true, \"verified\": true, \"global\": true, \"usedByDefault\": true }\n    ]\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Obtener un enlace corto",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "Un enlace tuyo por dominio y código.\n\nDevuelve un enlace de tu organización con sus contadores de toda la vida. Uno eliminado, o de otra organización, responde `404`. Uno bloqueado por Hablame se devuelve con `status: blocked`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Un enlace tuyo por dominio y código.\n\nDevuelve un enlace de tu organización con sus contadores de toda la vida. Uno eliminado, o de otra organización, responde `404`. Uno bloqueado por Hablame se devuelve con `status: blocked`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Actualizar un enlace corto",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «Actualizar un enlace corto» porque tiene efectos (crea, modifica, borra, envía, llama o cobra). Pon confirmoEfectos en si en el entorno para enviarla.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"disabled\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": {
              "content": "Cambia el destino, el estado o el vencimiento. El dominio y el código no cambian.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCambia la URL de destino (`longUrl`, que se revalida como al crear), el estado (`status`) o el vencimiento (`expiresAt`). Envía solo lo que quieras cambiar; hace falta al menos un campo. El dominio y el código no se pueden cambiar: romperían todas las URL cortas ya repartidas. Editar no tiene costo.\n\nCada cambio queda registrado con el valor anterior, el nuevo y quién lo hizo (la API key o el usuario del portal). Un enlace bloqueado por Hablame (`status: blocked`) no se puede editar: responde `409 LINK_BLOCKED`.\n\n> Las claves que no reconoce **se ignoran**: un cuerpo como `{\"active\": false}` (del contrato anterior) no cambia nada y responde `400 NOTHING_TO_UPDATE`. Para desactivar, envía `{\"status\": \"disabled\"}`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Modifica datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Si con este cambio el enlace vuelve a redirigir (lo reactivas, o le amplías o quitas el vencimiento), cada visita de una persona se vuelve a cobrar (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"status\": \"disabled\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": {
                  "content": "Cambia el destino, el estado o el vencimiento. El dominio y el código no cambian.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nCambia la URL de destino (`longUrl`, que se revalida como al crear), el estado (`status`) o el vencimiento (`expiresAt`). Envía solo lo que quieras cambiar; hace falta al menos un campo. El dominio y el código no se pueden cambiar: romperían todas las URL cortas ya repartidas. Editar no tiene costo.\n\nCada cambio queda registrado con el valor anterior, el nuevo y quién lo hizo (la API key o el usuario del portal). Un enlace bloqueado por Hablame (`status: blocked`) no se puede editar: responde `409 LINK_BLOCKED`.\n\n> Las claves que no reconoce **se ignoran**: un cuerpo como `{\"active\": false}` (del contrato anterior) no cambia nada y responde `400 NOTHING_TO_UPDATE`. Para desactivar, envía `{\"status\": \"disabled\"}`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Modifica datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Si con este cambio el enlace vuelve a redirigir (lo reactivas, o le amplías o quitas el vencimiento), cada visita de una persona se vuelve a cobrar (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Eliminar un enlace corto",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «Eliminar un enlace corto» porque tiene efectos (crea, modifica, borra, envía, llama o cobra). Pon confirmoEfectos en si en el entorno para enviarla.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([204]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"No trae cuerpo\", function () {",
                  "  pm.expect(pm.response.text()).to.equal('');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "El enlace deja de redirigir y desaparece de la API. Su código queda ocupado, y lo puedes restaurar durante 90 días.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nBorrado **lógico**: el enlace deja de redirigir de inmediato (el visitante recibe `404`) y desaparece de todas las respuestas de la API, estadísticas incluidas. Lo cobrado se conserva, y queda registrado quién lo eliminó. Su código queda ocupado para siempre en ese dominio: nadie puede crear otro enlace con él, pero tú puedes [restaurar este](https://developers.hablame.co/docs/reference/urlshortener/restore), sin nuevo cobro, **durante 90 días**. Después el enlace se archiva con sus visitas y ya no se puede restaurar; el código sigue ocupado.\n\nUn enlace bloqueado por Hablame (`status: blocked`) no se puede eliminar: responde `409 LINK_BLOCKED`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Borra datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "204 No Content · Ejemplo",
              "originalRequest": {
                "method": "DELETE",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "El enlace deja de redirigir y desaparece de la API. Su código queda ocupado, y lo puedes restaurar durante 90 días.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nBorrado **lógico**: el enlace deja de redirigir de inmediato (el visitante recibe `404`) y desaparece de todas las respuestas de la API, estadísticas incluidas. Lo cobrado se conserva, y queda registrado quién lo eliminó. Su código queda ocupado para siempre en ese dominio: nadie puede crear otro enlace con él, pero tú puedes [restaurar este](https://developers.hablame.co/docs/reference/urlshortener/restore), sin nuevo cobro, **durante 90 días**. Después el enlace se archiva con sus visitas y ya no se puede restaurar; el código sigue ocupado.\n\nUn enlace bloqueado por Hablame (`status: blocked`) no se puede eliminar: responde `409 LINK_BLOCKED`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Borra datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
                  "type": "text/markdown"
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "cookie": [],
              "body": ""
            }
          ]
        },
        {
          "name": "Restaurar un enlace eliminado",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «Restaurar un enlace eliminado» porque tiene efectos (crea, modifica, borra, envía, llama o cobra). Pon confirmoEfectos en si en el entorno para enviarla.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/restore",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code",
                "restore"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "Devuelve a la vida un enlace tuyo que eliminaste, con el mismo código y sin nuevo cobro.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nEl código de un enlace eliminado no se libera nunca, así que no lo puedes volver a crear (crear con ese alias responde `409 ALIAS_RETIRED`). Con este endpoint lo restauras: vuelve con su destino, su vencimiento y **el estado que tenía al eliminarlo** (si estaba desactivado, vuelve desactivado), y sus visitas de antes siguen contando. Restaurarlo no se cobra; si vuelve activo, sus visitas siguientes sí se cobran, como las de cualquier enlace activo. Se puede **durante 90 días** desde que lo eliminaste: después el enlace se archiva y restaurarlo responde `404 LINK_NOT_FOUND`.\n\n- Restaurar un enlace que no está eliminado lo devuelve tal cual: puedes reintentar sin miedo.\n- Un enlace bloqueado por Hablame no se puede restaurar (`409 LINK_BLOCKED`).\n- Queda registrado quién lo restauró. Comparte el límite de uso de la edición.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Modifica datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Si el enlace estaba activo cuando lo eliminaste, vuelve activo y cada visita de una persona se vuelve a cobrar (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/restore",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code",
                    "restore"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Devuelve a la vida un enlace tuyo que eliminaste, con el mismo código y sin nuevo cobro.\n\n**Esta petición tiene efectos y no se envía sola.** Para enviarla, pon `confirmoEfectos` en `si` en el entorno.\n\nEl código de un enlace eliminado no se libera nunca, así que no lo puedes volver a crear (crear con ese alias responde `409 ALIAS_RETIRED`). Con este endpoint lo restauras: vuelve con su destino, su vencimiento y **el estado que tenía al eliminarlo** (si estaba desactivado, vuelve desactivado), y sus visitas de antes siguen contando. Restaurarlo no se cobra; si vuelve activo, sus visitas siguientes sí se cobran, como las de cualquier enlace activo. Se puede **durante 90 días** desde que lo eliminaste: después el enlace se archiva y restaurarlo responde `404 LINK_NOT_FOUND`.\n\n- Restaurar un enlace que no está eliminado lo devuelve tal cual: puedes reintentar sin miedo.\n- Un enlace bloqueado por Hablame no se puede restaurar (`409 LINK_BLOCKED`).\n- Queda registrado quién lo restauró. Comparte el límite de uso de la edición.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Modifica datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Si el enlace estaba activo cuando lo eliminaste, vuelve activo y cada visita de una persona se vuelve a cobrar (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Estadísticas del enlace",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code",
                "stats"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-09-01",
                  "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "Totales del rango, serie por día o por hora y desgloses por país, dispositivo, sistema, navegador u origen.\n\nAnalítica de un enlace en una ventana de días: los totales del rango, una serie por día o por hora y los valores con más visitas de cada dimensión. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `totals.clicks` y `totals.uniqueClicks` son los del rango; `firstClickAt` y `lastClickAt`, los de toda la vida del enlace.\n- La serie es **dispersa**: los días u horas sin visitas no vienen. Complétalos en cero si vas a graficar.\n- Cada dimensión trae los `top` valores con más visitas y, si queda resto, una fila `__other`. Una visita sin dato para una dimensión (sin país, o llegada sin origen) no aparece en ella: sus filas pueden sumar menos que el total.\n- `referrer` es solo el dominio de origen (`www.facebook.com`), sin la ruta. Las visitas directas no tienen fila: son `totals.clicks` menos la suma de `referrer`.\n- `device` es `mobile`, `tablet` o `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` u `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` u `Other`. `country` es el código ISO (`CO`).\n\n> `uniqueClicks` cuenta la primera visita del día desde cada navegador. En un rango de varios días es la **suma** de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión.\n- Además, cada consulta gasta unidades de un cupo de 120 por minuto de tu organización, según su costo: 2 por la consulta y 3 por cada 31 días del rango. 30 días gastan 5; un año, 38. Sin unidades, `429` con `Retry-After`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/stats",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code",
                    "stats"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-09-01",
                      "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Totales del rango, serie por día o por hora y desgloses por país, dispositivo, sistema, navegador u origen.\n\nAnalítica de un enlace en una ventana de días: los totales del rango, una serie por día o por hora y los valores con más visitas de cada dimensión. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `totals.clicks` y `totals.uniqueClicks` son los del rango; `firstClickAt` y `lastClickAt`, los de toda la vida del enlace.\n- La serie es **dispersa**: los días u horas sin visitas no vienen. Complétalos en cero si vas a graficar.\n- Cada dimensión trae los `top` valores con más visitas y, si queda resto, una fila `__other`. Una visita sin dato para una dimensión (sin país, o llegada sin origen) no aparece en ella: sus filas pueden sumar menos que el total.\n- `referrer` es solo el dominio de origen (`www.facebook.com`), sin la ruta. Las visitas directas no tienen fila: son `totals.clicks` menos la suma de `referrer`.\n- `device` es `mobile`, `tablet` o `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` u `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` u `Other`. `country` es el código ISO (`CO`).\n\n> `uniqueClicks` cuenta la primera visita del día desde cada navegador. En un rango de varios días es la **suma** de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión.\n- Además, cada consulta gasta unidades de un cupo de 120 por minuto de tu organización, según su costo: 2 por la consulta y 3 por cada 31 días del rango. 30 días gastan 5; un año, 38. Sin unidades, `429` con `Retry-After`.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"range\": { \"from\": \"2026-08-25\", \"to\": \"2026-09-23\", \"granularity\": \"day\", \"timezone\": \"America/Bogota\" },\n    \"totals\": {\n      \"clicks\": 128,\n      \"uniqueClicks\": 96,\n      \"firstClickAt\": \"2026-09-20T15:31:40Z\",\n      \"lastClickAt\": \"2026-09-23T18:22:10Z\"\n    },\n    \"series\": [\n      { \"at\": \"2026-09-20\", \"clicks\": 40, \"uniqueClicks\": 31 },\n      { \"at\": \"2026-09-23\", \"clicks\": 88, \"uniqueClicks\": 65 }\n    ],\n    \"dimensions\": {\n      \"country\": [{ \"value\": \"CO\", \"clicks\": 110 }, { \"value\": \"__other\", \"clicks\": 12 }],\n      \"device\": [{ \"value\": \"mobile\", \"clicks\": 101 }, { \"value\": \"desktop\", \"clicks\": 27 }]\n    }\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Resumen de la organización",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/overview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "overview"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01",
                  "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                  "disabled": true
                },
                {
                  "key": "compare",
                  "value": "none",
                  "description": "`previous` agrega el periodo anterior de igual longitud (`previous`, `previousTotals` y, con `series`, `previousSeries`). `none`, o no enviarlo, lo omite. Hasta el 2026-09-30 el valor por defecto era `previous`.",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "series,top,recent,dimensions,heatmap",
                  "description": "Bloques opcionales, separados por coma: `series`, `top`, `recent`, `dimensions`, `heatmap`. Por defecto `series,top,recent`. Un nombre que no existe responde `400`.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Inventario, actividad del rango frente al periodo anterior, evolución, más visitados y desgloses de toda tu organización.\n\nTodo el acortador de tu organización en una sola llamada, sin recorrer los enlaces uno por uno. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `inventory` es una foto del momento y **no depende del rango**: los enlaces vivos por estado (`expired` son los activos con el vencimiento ya pasado), los alias, los que nunca han recibido una visita y las visitas de toda la vida.\n- `totals` es lo que pasó en el rango, y `today`, lo mismo para hoy. **El periodo anterior se pide con `compare=previous`**: entonces llegan `previous`, `previousTotals` y, con `series`, `previousSeries`, para el periodo anterior de igual longitud (30 días hasta hoy se comparan con los 30 anteriores, no con el mes calendario). Sin pedirlo llegan en `null`.\n- Los bloques opcionales se piden en `include`. Los que no pides llegan en `null`; los que pides sin datos, vacíos.\n\n> Las visitas únicas son la suma de las de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n> **Cambio del 2026-09-30:** antes el periodo anterior llegaba siempre; ahora solo con `compare=previous`. Si lo usas, agrega el parámetro.\n\nEl resumen recorre todos los enlaces de tu organización, así que su cupo va **según lo que cuesta**: cada resumen gasta unidades de un cupo de 150 por minuto de tu organización. Gasta 2 por la consulta; 1 por cada bloque que no depende del largo del rango (`inventory`, `today`, `recent`, `heatmap`), y, por cada 31 días del rango, 1 por cada bloque que lo recorre (`totals`, `series`, `top`, `dimensions`, y los del periodo anterior si comparas). Un resumen de 30 días con `series,top,recent` gasta 8; comparando, 10; uno de un año con todo, 78. Sin unidades, `429` con `Retry-After`. Y como mucho dos resúmenes de tu organización se calculan a la vez: el que llega con dos en curso espera unos segundos y, si no le toca turno, recibe `429`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión y por ranking.\n- Cupo por costo: 150 unidades por minuto por organización (ver arriba). Dos resúmenes a la vez por organización.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/overview",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "overview"
                  ],
                  "query": [
                    {
                      "key": "from",
                      "value": "2026-09-01",
                      "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                      "disabled": true
                    },
                    {
                      "key": "compare",
                      "value": "none",
                      "description": "`previous` agrega el periodo anterior de igual longitud (`previous`, `previousTotals` y, con `series`, `previousSeries`). `none`, o no enviarlo, lo omite. Hasta el 2026-09-30 el valor por defecto era `previous`.",
                      "disabled": true
                    },
                    {
                      "key": "include",
                      "value": "series,top,recent,dimensions,heatmap",
                      "description": "Bloques opcionales, separados por coma: `series`, `top`, `recent`, `dimensions`, `heatmap`. Por defecto `series,top,recent`. Un nombre que no existe responde `400`.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Inventario, actividad del rango frente al periodo anterior, evolución, más visitados y desgloses de toda tu organización.\n\nTodo el acortador de tu organización en una sola llamada, sin recorrer los enlaces uno por uno. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `inventory` es una foto del momento y **no depende del rango**: los enlaces vivos por estado (`expired` son los activos con el vencimiento ya pasado), los alias, los que nunca han recibido una visita y las visitas de toda la vida.\n- `totals` es lo que pasó en el rango, y `today`, lo mismo para hoy. **El periodo anterior se pide con `compare=previous`**: entonces llegan `previous`, `previousTotals` y, con `series`, `previousSeries`, para el periodo anterior de igual longitud (30 días hasta hoy se comparan con los 30 anteriores, no con el mes calendario). Sin pedirlo llegan en `null`.\n- Los bloques opcionales se piden en `include`. Los que no pides llegan en `null`; los que pides sin datos, vacíos.\n\n> Las visitas únicas son la suma de las de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n> **Cambio del 2026-09-30:** antes el periodo anterior llegaba siempre; ahora solo con `compare=previous`. Si lo usas, agrega el parámetro.\n\nEl resumen recorre todos los enlaces de tu organización, así que su cupo va **según lo que cuesta**: cada resumen gasta unidades de un cupo de 150 por minuto de tu organización. Gasta 2 por la consulta; 1 por cada bloque que no depende del largo del rango (`inventory`, `today`, `recent`, `heatmap`), y, por cada 31 días del rango, 1 por cada bloque que lo recorre (`totals`, `series`, `top`, `dimensions`, y los del periodo anterior si comparas). Un resumen de 30 días con `series,top,recent` gasta 8; comparando, 10; uno de un año con todo, 78. Sin unidades, `429` con `Retry-After`. Y como mucho dos resúmenes de tu organización se calculan a la vez: el que llega con dos en curso espera unos segundos y, si no le toca turno, recibe `429`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión y por ranking.\n- Cupo por costo: 150 unidades por minuto por organización (ver arriba). Dos resúmenes a la vez por organización.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"range\": { \"from\": \"2026-08-25\", \"to\": \"2026-09-23\", \"granularity\": \"day\", \"timezone\": \"America/Bogota\" },\n    \"previous\": { \"from\": \"2026-07-26\", \"to\": \"2026-08-24\" },\n    \"inventory\": {\n      \"total\": 1250, \"active\": 1190, \"expired\": 40, \"disabled\": 20, \"blocked\": 0,\n      \"aliases\": 35, \"neverClicked\": 610, \"clicks\": 48210, \"uniqueClicks\": 30115\n    },\n    \"today\": { \"linksCreated\": 42, \"clicks\": 1310, \"uniqueClicks\": 902, \"linksWithClicks\": 118 },\n    \"totals\": { \"linksCreated\": 820, \"clicks\": 21400, \"uniqueClicks\": 14020, \"linksWithClicks\": 540 },\n    \"previousTotals\": { \"linksCreated\": 610, \"clicks\": 17950, \"uniqueClicks\": 11800, \"linksWithClicks\": 455 },\n    \"series\": [\n      { \"at\": \"2026-08-25\", \"linksCreated\": 25, \"clicks\": 690, \"uniqueClicks\": 455 }\n    ],\n    \"previousSeries\": [\n      { \"at\": \"2026-07-26\", \"linksCreated\": 18, \"clicks\": 540, \"uniqueClicks\": 360 }\n    ],\n    \"topLinks\": [\n      {\n        \"domain\": \"h0b.co\", \"code\": \"promo-septiembre\", \"shortUrl\": \"https://h0b.co/promo-septiembre\",\n        \"longUrl\": \"https://example.com/promo\", \"status\": \"active\", \"expiresAt\": null,\n        \"clicks\": 3120, \"uniqueClicks\": 2210, \"lastClickAt\": \"2026-09-23T18:22:10Z\"\n      }\n    ],\n    \"dimensions\": null,\n    \"recent\": { \"created\": [], \"clicked\": [] },\n    \"heatmap\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "Precio vigente",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/pricing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "pricing"
              ]
            },
            "description": {
              "content": "Cuánto le cuesta a tu organización crear un enlace y cada visita, y si su plan lo incluye.\n\nDevuelve los precios que se le aplican hoy a tu organización: los generales del servicio o los propios que tenga pactados. Se cobra la creación de cada enlace y **cada visita de una persona** que llega al destino, también las repetidas. No se cobran las visitas de robots ni las vistas previas que arman las aplicaciones de mensajería (WhatsApp, Telegram, Facebook, Google Messages y similares) cuando alguien comparte el enlace.\n\nLos importes van en **micros**: unidades de la moneda multiplicadas por 10⁶ (`1000000` es 1,00). `includedInPlan: true` quiere decir que tu organización tiene hoy un plan mensual del acortador vigente: crear enlaces y las visitas van incluidos. Los precios llegan igual, para saber cuánto cubre el plan; si no hay tarifa configurada, llegan en `null`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/pricing",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "pricing"
                  ]
                },
                "description": {
                  "content": "Cuánto le cuesta a tu organización crear un enlace y cada visita, y si su plan lo incluye.\n\nDevuelve los precios que se le aplican hoy a tu organización: los generales del servicio o los propios que tenga pactados. Se cobra la creación de cada enlace y **cada visita de una persona** que llega al destino, también las repetidas. No se cobran las visitas de robots ni las vistas previas que arman las aplicaciones de mensajería (WhatsApp, Telegram, Facebook, Google Messages y similares) cuando alguien comparte el enlace.\n\nLos importes van en **micros**: unidades de la moneda multiplicadas por 10⁶ (`1000000` es 1,00). `includedInPlan: true` quiere decir que tu organización tiene hoy un plan mensual del acortador vigente: crear enlaces y las visitas van incluidos. Los precios llegan igual, para saber cuánto cubre el plan; si no hay tarifa configurada, llegan en `null`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"currency\": \"COP\",\n    \"perLinkMicro\": 1000000,\n    \"perUniqueVisitMicro\": 500000,\n    \"includedInPlan\": false\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Almacenamiento",
      "description": {
        "content": "Consulta lo que guarda tu organización: uso, consumo, archivos, enlaces de descarga, conservación y tarifa. Solo lectura; el almacenamiento nunca se pausa.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Resumen",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "summary"
              ]
            },
            "description": {
              "content": "Lo que tu organización tiene guardado ahora, lo que lleva el mes y lo que se elimina pronto.\n\nDevuelve lo que ocupan ahora tus archivos (en total, por servicio y por tipo de archivo), la tarifa que rige hoy, el consumo del mes en curso y cuántos archivos se eliminan en las próximas 24 horas y 7 días.\n\n`month.costMicro` es lo ya consolidado hasta `month.through`; `month.estimatedTodayMicro` estima lo que falta consolidar (normalmente hoy). `price` llega en `null` si tu organización todavía no tiene moneda definitiva.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/summary",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "summary"
                  ]
                },
                "description": {
                  "content": "Lo que tu organización tiene guardado ahora, lo que lleva el mes y lo que se elimina pronto.\n\nDevuelve lo que ocupan ahora tus archivos (en total, por servicio y por tipo de archivo), la tarifa que rige hoy, el consumo del mes en curso y cuántos archivos se eliminan en las próximas 24 horas y 7 días.\n\n`month.costMicro` es lo ya consolidado hasta `month.through`; `month.estimatedTodayMicro` estima lo que falta consolidar (normalmente hoy). `price` llega en `null` si tu organización todavía no tiene moneda definitiva.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"currency\": \"COP\",\n    \"now\": {\n      \"bytes\": 5368709120,\n      \"objects\": 1240,\n      \"byService\": [{ \"service\": \"callblasting\", \"name\": \"Llamadas\", \"bytes\": 5368709120, \"objects\": 1240 }],\n      \"byScope\": [{ \"service\": \"callblasting\", \"scope\": \"recordings\", \"name\": \"Grabaciones\", \"bytes\": 5368709120, \"objects\": 1240 }]\n    },\n    \"price\": { \"pricePerGbMonthMicro\": 1000000, \"source\": \"base\", \"effectiveFrom\": \"2026-10-01\" },\n    \"month\": { \"period\": \"2026-10\", \"gbMonths\": \"3.120000\", \"costMicro\": 3120000, \"through\": \"2026-10-18\", \"estimatedTodayMicro\": 170000 },\n    \"expiringSoon\": { \"h24\": { \"objects\": 12, \"bytes\": 50331648 }, \"d7\": { \"objects\": 80, \"bytes\": 335544320 } }\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        },
        {
          "name": "Consumo",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/usage",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "usage"
              ],
              "query": [
                {
                  "key": "group",
                  "value": "day",
                  "description": "Cómo se agrupa la serie.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-10-01",
                  "description": "Inicio del rango (incluido).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-10-19",
                  "description": "Fin del rango (excluido).",
                  "disabled": true
                },
                {
                  "key": "service",
                  "value": "callblasting",
                  "description": "Código del servicio que guardó los archivos (el `service` de las respuestas). Un código que no existe responde `400 INVALID_FILTER`.",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "recordings",
                  "description": "Código del tipo de archivo dentro del servicio (el `scope` de las respuestas). Exige `service`.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Serie del almacenamiento usado (GB al mes y costo) por día, por mes o por hora.\n\nDevuelve una serie con lo que usaste en cada periodo, el total del rango y el del rango anterior de la misma duración, para comparar. Por día o por mes, `from` y `to` son fechas `YYYY-MM-DD` (hora de Colombia, `to` excluido) y el rango es de hasta 400 días; sin ellos, el mes en curso. Por hora, son instantes ISO-8601 (sin zona se leen en hora de Colombia), el rango es de hasta 7 días y el costo de cada punto llega en `null`: el costo se calcula por día.\n\n`byteSeconds` es el almacenamiento exacto (bytes por segundos guardados) y llega como texto porque puede pasar del entero más grande que JSON representa sin perder precisión.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/usage",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "usage"
                  ],
                  "query": [
                    {
                      "key": "group",
                      "value": "day",
                      "description": "Cómo se agrupa la serie.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-10-01",
                      "description": "Inicio del rango (incluido).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-10-19",
                      "description": "Fin del rango (excluido).",
                      "disabled": true
                    },
                    {
                      "key": "service",
                      "value": "callblasting",
                      "description": "Código del servicio que guardó los archivos (el `service` de las respuestas). Un código que no existe responde `400 INVALID_FILTER`.",
                      "disabled": true
                    },
                    {
                      "key": "scope",
                      "value": "recordings",
                      "description": "Código del tipo de archivo dentro del servicio (el `scope` de las respuestas). Exige `service`.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Serie del almacenamiento usado (GB al mes y costo) por día, por mes o por hora.\n\nDevuelve una serie con lo que usaste en cada periodo, el total del rango y el del rango anterior de la misma duración, para comparar. Por día o por mes, `from` y `to` son fechas `YYYY-MM-DD` (hora de Colombia, `to` excluido) y el rango es de hasta 400 días; sin ellos, el mes en curso. Por hora, son instantes ISO-8601 (sin zona se leen en hora de Colombia), el rango es de hasta 7 días y el costo de cada punto llega en `null`: el costo se calcula por día.\n\n`byteSeconds` es el almacenamiento exacto (bytes por segundos guardados) y llega como texto porque puede pasar del entero más grande que JSON representa sin perder precisión.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"currency\": \"COP\",\n    \"series\": [\n      { \"period\": \"2026-10-01\", \"byteSeconds\": \"463856467968000\", \"gbMonths\": \"0.178957\", \"costMicro\": 178957, \"averageBytes\": 5368709120 }\n    ],\n    \"total\": { \"byteSeconds\": \"463856467968000\", \"gbMonths\": \"0.178957\", \"costMicro\": 178957, \"averageBytes\": 5368709120 },\n    \"comparison\": { \"previous\": { \"byteSeconds\": \"0\", \"gbMonths\": \"0.000000\", \"costMicro\": 0, \"averageBytes\": 0 } }\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        },
        {
          "name": "Listar archivos",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/objects",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "objects"
              ],
              "query": [
                {
                  "key": "service",
                  "value": "callblasting",
                  "description": "Código del servicio que guardó los archivos (el `service` de las respuestas). Un código que no existe responde `400 INVALID_FILTER`.",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "recordings",
                  "description": "Código del tipo de archivo dentro del servicio (el `scope` de las respuestas). Exige `service`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "",
                  "description": "Solo los guardados desde este instante (ISO-8601; sin zona, hora de Colombia).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "",
                  "description": "Solo los guardados antes de este instante.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Archivos por página.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "El `next` de la página anterior.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Los archivos de tu organización, del más reciente al más antiguo, paginados por cursor.\n\nLista los archivos guardados y los que se están eliminando, del más reciente al más antiguo. Pagina por cursor: si `next` no es `null`, pásalo como `cursor` para la página siguiente **con los mismos filtros**. Un cursor alterado, de otra organización, con otros filtros o de hace más de 24 horas responde `400 INVALID_CURSOR`: vuelve a la primera página.\n\n> **Los archivos piden una key con Almacenamiento** Los archivos pueden ser grabaciones de llamadas, audios o documentos de tu organización. Los ve cualquier API key con el servicio Almacenamiento (`storage`), y solo el propietario de tu organización crea las keys. Sin el servicio, la key recibe `403 AUTH_SERVICE_NOT_ALLOWED` aquí y en las demás rutas de Almacenamiento.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/objects",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "objects"
                  ],
                  "query": [
                    {
                      "key": "service",
                      "value": "callblasting",
                      "description": "Código del servicio que guardó los archivos (el `service` de las respuestas). Un código que no existe responde `400 INVALID_FILTER`.",
                      "disabled": true
                    },
                    {
                      "key": "scope",
                      "value": "recordings",
                      "description": "Código del tipo de archivo dentro del servicio (el `scope` de las respuestas). Exige `service`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "",
                      "description": "Solo los guardados desde este instante (ISO-8601; sin zona, hora de Colombia).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "",
                      "description": "Solo los guardados antes de este instante.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "20",
                      "description": "Archivos por página.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "El `next` de la página anterior.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Los archivos de tu organización, del más reciente al más antiguo, paginados por cursor.\n\nLista los archivos guardados y los que se están eliminando, del más reciente al más antiguo. Pagina por cursor: si `next` no es `null`, pásalo como `cursor` para la página siguiente **con los mismos filtros**. Un cursor alterado, de otra organización, con otros filtros o de hace más de 24 horas responde `400 INVALID_CURSOR`: vuelve a la primera página.\n\n> **Los archivos piden una key con Almacenamiento** Los archivos pueden ser grabaciones de llamadas, audios o documentos de tu organización. Los ve cualquier API key con el servicio Almacenamiento (`storage`), y solo el propietario de tu organización crea las keys. Sin el servicio, la key recibe `403 AUTH_SERVICE_NOT_ALLOWED` aquí y en las demás rutas de Almacenamiento.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"objects\": [\n      {\n        \"id\": \"obj_01JABCDEFGHJKMNPQRSTVWXYZ0\",\n        \"name\": \"llamada-3001234567.wav\",\n        \"service\": \"callblasting\",\n        \"scope\": \"recordings\",\n        \"size\": 482113,\n        \"contentType\": \"audio/wav\",\n        \"storedAt\": \"2026-10-18T10:00:00.000-05:00\",\n        \"expiresAt\": \"2026-11-17T10:00:00.000-05:00\",\n        \"status\": \"available\",\n        \"deletable\": false\n      }\n    ],\n    \"next\": \"c1.t3b9X0k...\"\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        },
        {
          "name": "Consultar un archivo",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/objects/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "objects",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "obj_01JABCDEFGHJKMNPQRSTVWXYZ0",
                  "description": "Identificador del archivo, tal como llega en el listado."
                }
              ]
            },
            "description": {
              "content": "El detalle de un archivo de tu organización.\n\nDevuelve un archivo, con la misma forma que en el listado.\n\n> **Los archivos piden una key con Almacenamiento** Los archivos pueden ser grabaciones de llamadas, audios o documentos de tu organización. Los ve cualquier API key con el servicio Almacenamiento (`storage`), y solo el propietario de tu organización crea las keys. Sin el servicio, la key recibe `403 AUTH_SERVICE_NOT_ALLOWED` aquí y en las demás rutas de Almacenamiento.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": []
        },
        {
          "name": "Enlace de descarga",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/objects/:id/download-url",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "objects",
                ":id",
                "download-url"
              ],
              "query": [
                {
                  "key": "minutes",
                  "value": "15",
                  "description": "Minutos de validez del enlace.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "obj_01JABCDEFGHJKMNPQRSTVWXYZ0",
                  "description": "Identificador del archivo, tal como llega en el listado."
                }
              ]
            },
            "description": {
              "content": "Un enlace temporal para descargar un archivo.\n\nDevuelve un enlace firmado para descargar el archivo, válido hasta `expiresAt` (15 minutos por omisión, como mucho 60) y nunca más allá de cuando el archivo se elimina. Cada enlace queda anotado con la API key que lo pidió y la IP desde la que llamaste. Un archivo que se está eliminando no se puede descargar: responde `404`.\n\nEl enlace es un secreto de corta vida: no lo guardes ni lo compartas. Pide uno nuevo cada vez que lo necesites.\n\n> **Los archivos piden una key con Almacenamiento** Los archivos pueden ser grabaciones de llamadas, audios o documentos de tu organización. Los ve cualquier API key con el servicio Almacenamiento (`storage`), y solo el propietario de tu organización crea las keys. Sin el servicio, la key recibe `403 AUTH_SERVICE_NOT_ALLOWED` aquí y en las demás rutas de Almacenamiento.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Rastro: Queda anotado en el registro de auditoría de tu organización que se pidió un enlace de descarga de ese archivo.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 2 solicitudes por segundo.\n- Cupo propio por organización, aparte del de las consultas.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/objects/:id/download-url",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "objects",
                    ":id",
                    "download-url"
                  ],
                  "query": [
                    {
                      "key": "minutes",
                      "value": "15",
                      "description": "Minutos de validez del enlace.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "obj_01JABCDEFGHJKMNPQRSTVWXYZ0",
                      "description": "Identificador del archivo, tal como llega en el listado."
                    }
                  ]
                },
                "description": {
                  "content": "Un enlace temporal para descargar un archivo.\n\nDevuelve un enlace firmado para descargar el archivo, válido hasta `expiresAt` (15 minutos por omisión, como mucho 60) y nunca más allá de cuando el archivo se elimina. Cada enlace queda anotado con la API key que lo pidió y la IP desde la que llamaste. Un archivo que se está eliminando no se puede descargar: responde `404`.\n\nEl enlace es un secreto de corta vida: no lo guardes ni lo compartas. Pide uno nuevo cada vez que lo necesites.\n\n> **Los archivos piden una key con Almacenamiento** Los archivos pueden ser grabaciones de llamadas, audios o documentos de tu organización. Los ve cualquier API key con el servicio Almacenamiento (`storage`), y solo el propietario de tu organización crea las keys. Sin el servicio, la key recibe `403 AUTH_SERVICE_NOT_ALLOWED` aquí y en las demás rutas de Almacenamiento.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Rastro: Queda anotado en el registro de auditoría de tu organización que se pidió un enlace de descarga de ese archivo.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 2 solicitudes por segundo.\n- Cupo propio por organización, aparte del de las consultas.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"url\": \"https://storage.hablame.co/cloud/v1/...?Expires=1790000000&KeyName=...&Signature=...\",\n    \"expiresAt\": \"2026-10-19T10:45:00.000-05:00\"\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        },
        {
          "name": "Conservación",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/retention",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "retention"
              ]
            },
            "description": {
              "content": "Cuánto tiempo se guarda cada tipo de archivo antes de eliminarse.\n\nDevuelve, por tipo de archivo, el tiempo de conservación vigente, el mínimo y el máximo que admite, el de por omisión, si tu organización eligió uno propio y quién lo cambió por última vez. `deletionInProgress.objects` cuenta los archivos que se están eliminando ahora. El tiempo se cambia desde el portal de clientes: aquí solo se consulta.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/retention",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "retention"
                  ]
                },
                "description": {
                  "content": "Cuánto tiempo se guarda cada tipo de archivo antes de eliminarse.\n\nDevuelve, por tipo de archivo, el tiempo de conservación vigente, el mínimo y el máximo que admite, el de por omisión, si tu organización eligió uno propio y quién lo cambió por última vez. `deletionInProgress.objects` cuenta los archivos que se están eliminando ahora. El tiempo se cambia desde el portal de clientes: aquí solo se consulta.\n\n> **Solo consulta; el almacenamiento nunca se pausa** Tus archivos se guardan de forma continua y nunca se pausan, aunque no tengas saldo. Para consultarlos por API, la API key necesita el servicio Almacenamiento (`storage`): con él ve todo (resumen, consumo, archivos, descargas, conservación y tarifa) y sin él cada una de estas rutas responde `403 AUTH_SERVICE_NOT_ALLOWED`. La API es **solo de consulta**: el tiempo de conservación se cambia desde el portal de clientes (Almacenamiento → Conservación) y un archivo se elimina desde ahí o cuando vence. Cualquier método que no sea `GET` responde `405`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"items\": [\n      {\n        \"service\": \"callblasting\",\n        \"scope\": \"recordings\",\n        \"name\": \"Grabaciones\",\n        \"seconds\": 2592000,\n        \"minSeconds\": 86400,\n        \"maxSeconds\": 31536000,\n        \"defaultSeconds\": 2592000,\n        \"custom\": false,\n        \"version\": 0,\n        \"updatedBy\": null,\n        \"updatedAt\": null,\n        \"deletionInProgress\": { \"objects\": 0 }\n      }\n    ]\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        },
        {
          "name": "Tarifa vigente",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/storage/pricing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "storage",
                "pricing"
              ]
            },
            "description": {
              "content": "El precio por GB al mes que se le aplica hoy a tu organización, y el próximo si ya está fijado.\n\nDevuelve la tarifa de hoy en la moneda de tu organización: la general (`base`) o una pactada (`custom`). `upcoming` es la que empezará a regir en una fecha futura, si ya está fijada. Si tu organización todavía no tiene moneda definitiva, los campos de precio llegan en `null`.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/storage/pricing",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "storage",
                    "pricing"
                  ]
                },
                "description": {
                  "content": "El precio por GB al mes que se le aplica hoy a tu organización, y el próximo si ya está fijado.\n\nDevuelve la tarifa de hoy en la moneda de tu organización: la general (`base`) o una pactada (`custom`). `upcoming` es la que empezará a regir en una fecha futura, si ya está fijada. Si tu organización todavía no tiene moneda definitiva, los campos de precio llegan en `null`.\n\nLos importes van en **micros** de la moneda de tu organización (`currency`, ISO 4217): unidades multiplicadas por 10⁶ (`1000000` es 1,00). El almacenamiento se cobra por **GB al mes** (GB decimal, 10⁹ bytes) según el tiempo que cada archivo estuvo guardado.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `storage` habilitado.\n- Límite: 10 solicitudes por segundo.\n- Las seis consultas de Almacenamiento (resumen, consumo, listado, detalle, conservación y tarifa) comparten este cupo por organización: no es un cupo por endpoint.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"currency\": \"COP\",\n    \"pricePerGbMonthMicro\": 1000000,\n    \"source\": \"base\",\n    \"effectiveFrom\": \"2026-10-01\",\n    \"upcoming\": null\n  },\n  \"meta\": {\n    \"requestId\": \"5d0f3a9c7b2e41a8\",\n    \"clientIp\": \"203.0.113.10\",\n    \"timestamp\": \"2026-10-19T15:30:00+00:00\",\n    \"responseTimeMs\": 12.4\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Organización",
      "description": {
        "content": "Perfil de la organización, centros de costo y usuarios. Incluye datos personales.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Organización",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/account/organization",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "account",
                "organization"
              ]
            },
            "description": {
              "content": "Perfil de la organización dueña de la API key.\n\nPerfil de la organización dueña de la API key: identidad y configuración. No incluye saldo ni cupo.\n\n> **Incluye datos personales** La API key debe tener habilitada explícitamente la capacidad `directory`. Tener acceso a todos los servicios NO la concede. Una key sin la capacidad recibe `403`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con la capacidad `directory` concedida.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/account/organization",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "account",
                    "organization"
                  ]
                },
                "description": {
                  "content": "Perfil de la organización dueña de la API key.\n\nPerfil de la organización dueña de la API key: identidad y configuración. No incluye saldo ni cupo.\n\n> **Incluye datos personales** La API key debe tener habilitada explícitamente la capacidad `directory`. Tener acceso a todos los servicios NO la concede. Una key sin la capacidad recibe `403`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con la capacidad `directory` concedida.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"id\": 10000003,\n    \"displayName\": \"Acme S.A.S.\",\n    \"currency\": \"COP\",\n    \"status\": \"active\",\n    \"blocks\": { \"general\": false, \"collections\": false, \"security\": false },\n    \"onboardedAt\": \"2026-05-14 11:31:56\"\n  },\n  \"meta\": {\n    \"requestId\": \"b9b1704baffab211\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-06-02T19:43:59+00:00\",\n    \"responseTimeMs\": 4.1\n  }\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Flujos: modifican datos y cobran",
      "description": {
        "content": "Peticiones encadenadas que **crean, consultan y borran** un enlace corto. Todo es real: crear el enlace se cobra y borrarlo cambia datos de tu organización. No se ejecutan solas: necesitan `confirmoFlujos` en `si`. Si un paso falla, el flujo se detiene.\n\nLos pasos comparten el dominio y el código del enlace creado mediante las variables de colección `flujoDominio` y `flujoCodigo`.",
        "type": "text/markdown"
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
              "  console.log(\"Hablame: se omitió «Flujos: modifican datos y cobran» porque forma parte de un flujo que cambia datos y cobra. Pon confirmoFlujos en si en el entorno para ejecutar el flujo.\");",
              "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
              "    pm.execution.skipRequest();",
              "  } else {",
              "    pm.request.url.update('https://bloqueado.invalid/');",
              "  }",
              "}"
            ]
          }
        }
      ],
      "item": [
        {
          "name": "1. Crear un enlace corto",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «1. Crear un enlace corto» porque forma parte de un flujo que cambia datos y cobra. Pon confirmoFlujos en si en el entorno para ejecutar el flujo.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([201]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});",
                  "pm.collectionVariables.set('flujoDominio', '');",
                  "pm.collectionVariables.set('flujoCodigo', '');",
                  "var enlace = null;",
                  "try { enlace = pm.response.json().data; } catch (e) { enlace = null; }",
                  "if (enlace && enlace.domain && enlace.code) {",
                  "  pm.collectionVariables.set('flujoDominio', enlace.domain);",
                  "  pm.collectionVariables.set('flujoCodigo', enlace.code);",
                  "} else {",
                  "  pm.execution.setNextRequest(null);",
                  "}",
                  "// Si el paso no salio como se esperaba, el flujo se detiene aqui.",
                  "if ([201].indexOf(pm.response.code) === -1) {",
                  "  pm.execution.setNextRequest(null);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Llave de idempotencia opcional (1 a 255 caracteres de `A-Za-z0-9_-`), única por organización. Si repites el alta con la misma llave y el mismo cuerpo, recibes el enlace que ya se creó, sin crear otro ni cobrarlo otra vez. Ver la [guía de idempotencia](https://developers.hablame.co/docs/idempotency)."
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"longUrl\": \"https://example.com/landing?utm_source=sms\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": {
              "content": "Publica un enlace corto con código generado o alias propio. Se cobra al crearlo, salvo que tu plan lo incluya.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nCrea un enlace corto en uno de los dominios disponibles para tu organización. Si no envías `code`, el servicio genera uno de 7 caracteres (`0-9a-z`); si lo envías, queda como alias (`isAlias: true`). Los códigos son únicos **por dominio**, y el de un enlace eliminado no se puede volver a usar para otro enlace: si era tuyo, puedes [restaurarlo](https://developers.hablame.co/docs/reference/urlshortener/restore) durante 90 días.\n\nCada enlace creado se cobra al precio vigente de tu organización (consúltalo con [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/docs/reference/urlshortener/pricing)). Si tu organización tiene un plan mensual del acortador vigente, crear enlaces no se cobra, y entonces rige un **cupo diario de altas** (100.000 por día de Colombia, salvo que tengas otro pactado): al llegar, la API responde `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` hasta la medianoche. Sin plan no hay cupo diario: sin saldo disponible la API responde `402` y el enlace no se crea.\n\n> **Reintenta sin duplicar con `Idempotency-Key`.** Si un alta se corta (un timeout, un `5xx`) y la repites con la misma llave y el mismo cuerpo, recibes el mismo enlace (`201` con `Idempotency-Status: replayed`) sin crear otro ni cobrarlo dos veces, aunque la primera todavía se esté procesando. La misma llave con otro cuerpo responde `422 IDEMPOTENCY_KEY_REUSED`. En este endpoint la llave queda asociada al enlace para siempre. Sin llave, cada reintento crea y cobra otro enlace.\n\n> El cuerpo se valida **antes** de comprobar el saldo: una URL, un alias, un dominio o un vencimiento inválidos responden su error sin tocar tu saldo.\n\n> **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`.\n\n¿Muchos enlaces a la vez, por ejemplo para una campaña? Usa el [alta en lote](https://developers.hablame.co/docs/reference/urlshortener/batch): hasta 1.000 por petición, con las mismas reglas y un solo cobro por lo que se creó.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- La URL de destino admite hasta 2048 caracteres; el cuerpo, hasta 64 KiB.\n- Se rechazan destinos en `localhost`, `*.local`, `*.internal` o en IP privadas o reservadas; destinos que ya son un enlace corto (nuestros dominios o acortadores como bit.ly, tinyurl.com o t.co); destinos con usuario o clave antes del dominio (`usuario@`); dominios con tildes, eñes u otros alfabetos (también en `xn--`), y URL con caracteres invisibles.\n- Con plan: hasta 100.000 altas por día de Colombia (cupo diario).\n- Admite `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "201 Created · Ejemplo",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Llave de idempotencia opcional (1 a 255 caracteres de `A-Za-z0-9_-`), única por organización. Si repites el alta con la misma llave y el mismo cuerpo, recibes el enlace que ya se creó, sin crear otro ni cobrarlo otra vez. Ver la [guía de idempotencia](https://developers.hablame.co/docs/idempotency)."
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links"
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"longUrl\": \"https://example.com/landing?utm_source=sms\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": {
                  "content": "Publica un enlace corto con código generado o alias propio. Se cobra al crearlo, salvo que tu plan lo incluya.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nCrea un enlace corto en uno de los dominios disponibles para tu organización. Si no envías `code`, el servicio genera uno de 7 caracteres (`0-9a-z`); si lo envías, queda como alias (`isAlias: true`). Los códigos son únicos **por dominio**, y el de un enlace eliminado no se puede volver a usar para otro enlace: si era tuyo, puedes [restaurarlo](https://developers.hablame.co/docs/reference/urlshortener/restore) durante 90 días.\n\nCada enlace creado se cobra al precio vigente de tu organización (consúltalo con [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/docs/reference/urlshortener/pricing)). Si tu organización tiene un plan mensual del acortador vigente, crear enlaces no se cobra, y entonces rige un **cupo diario de altas** (100.000 por día de Colombia, salvo que tengas otro pactado): al llegar, la API responde `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` hasta la medianoche. Sin plan no hay cupo diario: sin saldo disponible la API responde `402` y el enlace no se crea.\n\n> **Reintenta sin duplicar con `Idempotency-Key`.** Si un alta se corta (un timeout, un `5xx`) y la repites con la misma llave y el mismo cuerpo, recibes el mismo enlace (`201` con `Idempotency-Status: replayed`) sin crear otro ni cobrarlo dos veces, aunque la primera todavía se esté procesando. La misma llave con otro cuerpo responde `422 IDEMPOTENCY_KEY_REUSED`. En este endpoint la llave queda asociada al enlace para siempre. Sin llave, cada reintento crea y cobra otro enlace.\n\n> El cuerpo se valida **antes** de comprobar el saldo: una URL, un alias, un dominio o un vencimiento inválidos responden su error sin tocar tu saldo.\n\n> **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`.\n\n¿Muchos enlaces a la vez, por ejemplo para una campaña? Usa el [alta en lote](https://developers.hablame.co/docs/reference/urlshortener/batch): hasta 1.000 por petición, con las mismas reglas y un solo cobro por lo que se creó.\n\n## Alcance y límites\n\n- Qué hace: Crea datos en tu organización.\n- Cobro: La llamada se cobra.\n- Después, cada visita de una persona al enlace también se cobra (las de robots y las vistas previas no).\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.\n- La URL de destino admite hasta 2048 caracteres; el cuerpo, hasta 64 KiB.\n- Se rechazan destinos en `localhost`, `*.local`, `*.internal` o en IP privadas o reservadas; destinos que ya son un enlace corto (nuestros dominios o acortadores como bit.ly, tinyurl.com o t.co); destinos con usuario o clave antes del dominio (`usuario@`); dominios con tildes, eñes u otros alfabetos (también en `xn--`), y URL con caracteres invisibles.\n- Con plan: hasta 100.000 altas por día de Colombia (cupo diario).\n- Admite `Idempotency-Key`.",
                  "type": "text/markdown"
                }
              },
              "status": "Created",
              "code": 201,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "2. Consultar el enlace creado",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «2. Consultar el enlace creado» porque forma parte de un flujo que cambia datos y cobra. Pon confirmoFlujos en si en el entorno para ejecutar el flujo.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});",
                  "// Si el paso no salio como se esperaba, el flujo se detiene aqui.",
                  "if ([200].indexOf(pm.response.code) === -1) {",
                  "  pm.execution.setNextRequest(null);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "Un enlace tuyo por dominio y código.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nDevuelve un enlace de tu organización con sus contadores de toda la vida. Uno eliminado, o de otra organización, responde `404`. Uno bloqueado por Hablame se devuelve con `status: blocked`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Un enlace tuyo por dominio y código.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nDevuelve un enlace de tu organización con sus contadores de toda la vida. Uno eliminado, o de otra organización, responde `404`. Uno bloqueado por Hablame se devuelve con `status: blocked`.\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 60 solicitudes por minuto.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"longUrl\": \"https://example.com/landing?utm_source=sms\",\n    \"isAlias\": false,\n    \"status\": \"active\",\n    \"domainStatus\": \"active\",\n    \"expiresAt\": null,\n    \"createdAt\": \"2026-09-23T15:04:05Z\",\n    \"clicks\": 0,\n    \"uniqueClicks\": 0,\n    \"firstClickAt\": null,\n    \"lastClickAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "3. Estadísticas del enlace",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «3. Estadísticas del enlace» porque forma parte de un flujo que cambia datos y cobra. Pon confirmoFlujos en si en el entorno para ejecutar el flujo.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"La respuesta es JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"Trae la envoltura de la API\", function () {",
                  "  var cuerpo = pm.response.json();",
                  "  pm.expect(cuerpo).to.have.property('success');",
                  "  if (cuerpo.success) { pm.expect(cuerpo).to.have.property('data'); }",
                  "  else { pm.expect(cuerpo.error).to.have.property('code'); }",
                  "});",
                  "pm.test(\"Trae meta.requestId\", function () {",
                  "  pm.expect(pm.response.json().meta).to.have.property('requestId');",
                  "});",
                  "// Si el paso no salio como se esperaba, el flujo se detiene aqui.",
                  "if ([200].indexOf(pm.response.code) === -1) {",
                  "  pm.execution.setNextRequest(null);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code",
                "stats"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-09-01",
                  "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "Totales del rango, serie por día o por hora y desgloses por país, dispositivo, sistema, navegador u origen.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nAnalítica de un enlace en una ventana de días: los totales del rango, una serie por día o por hora y los valores con más visitas de cada dimensión. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `totals.clicks` y `totals.uniqueClicks` son los del rango; `firstClickAt` y `lastClickAt`, los de toda la vida del enlace.\n- La serie es **dispersa**: los días u horas sin visitas no vienen. Complétalos en cero si vas a graficar.\n- Cada dimensión trae los `top` valores con más visitas y, si queda resto, una fila `__other`. Una visita sin dato para una dimensión (sin país, o llegada sin origen) no aparece en ella: sus filas pueden sumar menos que el total.\n- `referrer` es solo el dominio de origen (`www.facebook.com`), sin la ruta. Las visitas directas no tienen fila: son `totals.clicks` menos la suma de `referrer`.\n- `device` es `mobile`, `tablet` o `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` u `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` u `Other`. `country` es el código ISO (`CO`).\n\n> `uniqueClicks` cuenta la primera visita del día desde cada navegador. En un rango de varios días es la **suma** de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión.\n- Además, cada consulta gasta unidades de un cupo de 120 por minuto de tu organización, según su costo: 2 por la consulta y 3 por cada 31 días del rango. 30 días gastan 5; un año, 38. Sin unidades, `429` con `Retry-After`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Ejemplo",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code/stats",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code",
                    "stats"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-09-01",
                      "description": "Primer día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, 29 días antes de `to` (la ventana es de 30 días).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Último día de la ventana, `AAAA-MM-DD` en hora de Colombia, incluido. Por defecto, hoy.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Tamaño de cada punto de la serie. `day` admite ventanas de hasta 366 días; `hour`, de hasta 31.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Dimensiones de desglose, separadas por coma: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Por defecto `country,device,os,browser,referrer`. Los nombres que no existen se ignoran.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "Cuántos valores trae cada dimensión (y cada ranking). Máximo 100.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "Totales del rango, serie por día o por hora y desgloses por país, dispositivo, sistema, navegador u origen.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nAnalítica de un enlace en una ventana de días: los totales del rango, una serie por día o por hora y los valores con más visitas de cada dimensión. Los días y las horas son de Colombia (`America/Bogota`).\n\n- `totals.clicks` y `totals.uniqueClicks` son los del rango; `firstClickAt` y `lastClickAt`, los de toda la vida del enlace.\n- La serie es **dispersa**: los días u horas sin visitas no vienen. Complétalos en cero si vas a graficar.\n- Cada dimensión trae los `top` valores con más visitas y, si queda resto, una fila `__other`. Una visita sin dato para una dimensión (sin país, o llegada sin origen) no aparece en ella: sus filas pueden sumar menos que el total.\n- `referrer` es solo el dominio de origen (`www.facebook.com`), sin la ruta. Las visitas directas no tienen fila: son `totals.clicks` menos la suma de `referrer`.\n- `device` es `mobile`, `tablet` o `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` u `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` u `Other`. `country` es el código ISO (`CO`).\n\n> `uniqueClicks` cuenta la primera visita del día desde cada navegador. En un rango de varios días es la **suma** de cada día, no personas distintas. Las respuestas pueden tener hasta un minuto de antigüedad (cabecera `X-Cache`).\n\n## Alcance y límites\n\n- Qué hace: Solo consulta: no cambia nada.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 120 solicitudes por minuto.\n- Ventanas de hasta 366 días por día, y de hasta 31 días por hora.\n- Hasta 100 valores por dimensión.\n- Además, cada consulta gasta unidades de un cupo de 120 por minuto de tu organización, según su costo: 2 por la consulta y 3 por cada 31 días del rango. 30 días gastan 5; un año, 38. Sin unidades, `429` con `Retry-After`.",
                  "type": "text/markdown"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json; charset=utf-8"
                }
              ],
              "cookie": [],
              "body": "{\n  \"success\": true,\n  \"data\": {\n    \"domain\": \"h0b.co\",\n    \"code\": \"a1b2c3d\",\n    \"shortUrl\": \"https://h0b.co/a1b2c3d\",\n    \"range\": { \"from\": \"2026-08-25\", \"to\": \"2026-09-23\", \"granularity\": \"day\", \"timezone\": \"America/Bogota\" },\n    \"totals\": {\n      \"clicks\": 128,\n      \"uniqueClicks\": 96,\n      \"firstClickAt\": \"2026-09-20T15:31:40Z\",\n      \"lastClickAt\": \"2026-09-23T18:22:10Z\"\n    },\n    \"series\": [\n      { \"at\": \"2026-09-20\", \"clicks\": 40, \"uniqueClicks\": 31 },\n      { \"at\": \"2026-09-23\", \"clicks\": 88, \"uniqueClicks\": 65 }\n    ],\n    \"dimensions\": {\n      \"country\": [{ \"value\": \"CO\", \"clicks\": 110 }, { \"value\": \"__other\", \"clicks\": 12 }],\n      \"device\": [{ \"value\": \"mobile\", \"clicks\": 101 }, { \"value\": \"desktop\", \"clicks\": 27 }]\n    }\n  },\n  \"meta\": {\n    \"requestId\": \"8f0c0e2a4b1d4c8f\",\n    \"clientIp\": \"203.0.113.45\",\n    \"timestamp\": \"2026-09-23T18:30:00+00:00\",\n    \"responseTimeMs\": 7.2\n  }\n}"
            }
          ]
        },
        {
          "name": "4. Eliminar el enlace",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: se omitió «4. Eliminar el enlace» porque forma parte de un flujo que cambia datos y cobra. Pon confirmoFlujos en si en el entorno para ejecutar el flujo.\");",
                  "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
                  "    pm.execution.skipRequest();",
                  "  } else {",
                  "    pm.request.url.update('https://bloqueado.invalid/');",
                  "  }",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"El estado es el esperado\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([204]);",
                  "});",
                  "pm.test(\"Trae la cabecera X-Request-Id\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"No trae cuerpo\", function () {",
                  "  pm.expect(pm.response.text()).to.equal('');",
                  "});",
                  "// Si el paso no salio como se esperaba, el flujo se detiene aqui.",
                  "if ([204].indexOf(pm.response.code) === -1) {",
                  "  pm.execution.setNextRequest(null);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v6",
                "urlshortener",
                "links",
                ":domain",
                ":code"
              ],
              "query": [
                {
                  "key": "domainStatus",
                  "value": "",
                  "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Código o alias del enlace. Se compara en minúsculas."
                }
              ]
            },
            "description": {
              "content": "El enlace deja de redirigir y desaparece de la API. Su código queda ocupado, y lo puedes restaurar durante 90 días.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nBorrado **lógico**: el enlace deja de redirigir de inmediato (el visitante recibe `404`) y desaparece de todas las respuestas de la API, estadísticas incluidas. Lo cobrado se conserva, y queda registrado quién lo eliminó. Su código queda ocupado para siempre en ese dominio: nadie puede crear otro enlace con él, pero tú puedes [restaurar este](https://developers.hablame.co/docs/reference/urlshortener/restore), sin nuevo cobro, **durante 90 días**. Después el enlace se archiva con sus visitas y ya no se puede restaurar; el código sigue ocupado.\n\nUn enlace bloqueado por Hablame (`status: blocked`) no se puede eliminar: responde `409 LINK_BLOCKED`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Borra datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "204 No Content · Ejemplo",
              "originalRequest": {
                "method": "DELETE",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v6/urlshortener/links/:domain/:code",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v6",
                    "urlshortener",
                    "links",
                    ":domain",
                    ":code"
                  ],
                  "query": [
                    {
                      "key": "domainStatus",
                      "value": "",
                      "description": "Con `released`, el enlace es el de un dominio que ya no es de tu organización (el que la lista devuelve con `domainStatus: released`), aunque ese nombre sea hoy el de otro dominio con el mismo código. Sin él, `domain` nombra al dominio que hoy se llama así. Otro valor responde `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Dominio del enlace, tal como lo devuelve la API en `domain`. Se compara en minúsculas. Nombra al enlace que hoy responde en esa URL corta; para uno de un dominio que ya no es de tu organización (`domainStatus: released`), añade `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Código o alias del enlace. Se compara en minúsculas."
                    }
                  ]
                },
                "description": {
                  "content": "El enlace deja de redirigir y desaparece de la API. Su código queda ocupado, y lo puedes restaurar durante 90 días.\n\n**Esta petición forma parte de un flujo que cambia datos y cobra.** Para ejecutarla, pon `confirmoFlujos` en `si` en el entorno.\n\nBorrado **lógico**: el enlace deja de redirigir de inmediato (el visitante recibe `404`) y desaparece de todas las respuestas de la API, estadísticas incluidas. Lo cobrado se conserva, y queda registrado quién lo eliminó. Su código queda ocupado para siempre en ese dominio: nadie puede crear otro enlace con él, pero tú puedes [restaurar este](https://developers.hablame.co/docs/reference/urlshortener/restore), sin nuevo cobro, **durante 90 días**. Después el enlace se archiva con sus visitas y ya no se puede restaurar; el código sigue ocupado.\n\nUn enlace bloqueado por Hablame (`status: blocked`) no se puede eliminar: responde `409 LINK_BLOCKED`.\n\n> **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`.\n\n## Alcance y límites\n\n- Qué hace: Borra datos de tu organización.\n- Cobro: La llamada no se cobra.\n- Acceso: API key con el servicio `urlshortener` habilitado.\n- Límite: 35 solicitudes por segundo.",
                  "type": "text/markdown"
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "cookie": [],
              "body": ""
            }
          ]
        }
      ]
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "if (!pm.variables.get('apiKey')) {",
          "  console.warn(\"Hablame: la variable apiKey está vacía. Pega tu clave de API en el entorno.\");",
          "}"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://developers.hablame.co",
      "type": "string",
      "description": "Dirección base de la API. No la cambies."
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Tu clave de API. Pégala en el entorno, nunca en la colección."
    },
    {
      "key": "flujoDominio",
      "value": "",
      "type": "string"
    },
    {
      "key": "flujoCodigo",
      "value": "",
      "type": "string"
    }
  ]
}