{
  "info": {
    "_postman_id": "9bf14125-070d-4c08-a539-56e1ab510c2b",
    "name": "Hablame API v6",
    "description": {
      "content": "Official collection of the Hablame API v6, generated from the same catalog as the documentation.\n\n**How to use it**\n1. Also import the \"Hablame · Production\" environment and select it at the top right.\n2. Paste your API key into the environment `apiKey` variable (it is secret and ships empty).\n3. Send \"Ping\" to check that the key works.\n\n**It is safe by default.** Any request that creates, changes, deletes, sends, calls or charges skips itself, also in the Runner, unless the environment has `confirmoEfectos` set to `si`. The \"Flows\" folder is also protected with `confirmoFlujos`. Everything you send is real and can be charged.",
      "type": "text/markdown"
    },
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "version": "6.0.0"
  },
  "item": [
    {
      "name": "Utilities",
      "description": {
        "content": "Diagnostic endpoints, available to any API key. Use them to verify your credentials are wired correctly.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Ping",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Confirms your API key is valid and shows what the API knows about the caller.\n\nReturns the caller state: it confirms the API key is valid and shows the key facts the API knows about the requester.\n\nUse it as a smoke test after configuring your credentials. A `200` means your integration is ready to call the rest of the endpoints.\n\n- That the key is valid (`pong: true`).\n- The API version that served the call.\n- The public IP the call originates from.\n\nYour organization details do **not** travel in this response: you can read them at `/api/v6/account/organization`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 20 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "Confirms your API key is valid and shows what the API knows about the caller.\n\nReturns the caller state: it confirms the API key is valid and shows the key facts the API knows about the requester.\n\nUse it as a smoke test after configuring your credentials. A `200` means your integration is ready to call the rest of the endpoints.\n\n- That the key is valid (`pong: true`).\n- The API version that served the call.\n- The public IP the call originates from.\n\nYour organization details do **not** travel in this response: you can read them at `/api/v6/account/organization`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 20 requests per minute.",
                  "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": "Tools",
      "description": {
        "content": "General-purpose catalogs you can consume from your app. Available to any API key.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "List countries",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "ISO-639-1 code. When specified, each country returns a flat `name` in that locale instead of the full `names` map.",
                  "disabled": true
                },
                {
                  "key": "callingCode",
                  "value": "57",
                  "description": "Filters countries by E.164 calling code (for example `57` for Colombia). An optional leading `+` is accepted.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "col",
                  "description": "Case-insensitive substring match on the country name in the requested locale. Minimum 2 characters.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Full country catalog with ISO code, calling code and MCC.\n\nReturns the full country catalog the API knows about, with ISO code, calling code, mobile country code (MCC) and names in English and Spanish. Use it to render country pickers, autodetect a country from a phone prefix, or resolve MCCs.\n\nThe catalog rarely changes. The response includes `Cache-Control: public, max-age=3600` so your client and any intermediate CDN can amortize the traffic.\n\nNames are provided only in English (`en`) and Spanish (`es`). Any other `lang` value falls back to English.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "ISO-639-1 code. When specified, each country returns a flat `name` in that locale instead of the full `names` map.",
                      "disabled": true
                    },
                    {
                      "key": "callingCode",
                      "value": "57",
                      "description": "Filters countries by E.164 calling code (for example `57` for Colombia). An optional leading `+` is accepted.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "col",
                      "description": "Case-insensitive substring match on the country name in the requested locale. Minimum 2 characters.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Full country catalog with ISO code, calling code and MCC.\n\nReturns the full country catalog the API knows about, with ISO code, calling code, mobile country code (MCC) and names in English and Spanish. Use it to render country pickers, autodetect a country from a phone prefix, or resolve MCCs.\n\nThe catalog rarely changes. The response includes `Cache-Control: public, max-age=3600` so your client and any intermediate CDN can amortize the traffic.\n\nNames are provided only in English (`en`) and Spanish (`es`). Any other `lang` value falls back to English.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 60 requests per minute.",
                  "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": "Get a country",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "ISO-639-1 locale. Returns a flat `name` in that locale instead of the full `names` map.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "CO",
                  "description": "ISO 3166-1 alpha-2 country code (`CO`, `MX`, `US`...). Case-insensitive."
                }
              ]
            },
            "description": {
              "content": "One country by its ISO 3166-1 alpha-2 code.\n\nReturns one country by its ISO 3166-1 alpha-2 code (2 letters, case-insensitive).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "ISO-639-1 locale. Returns a flat `name` in that locale instead of the full `names` map.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "CO",
                      "description": "ISO 3166-1 alpha-2 country code (`CO`, `MX`, `US`...). Case-insensitive."
                    }
                  ]
                },
                "description": {
                  "content": "One country by its ISO 3166-1 alpha-2 code.\n\nReturns one country by its ISO 3166-1 alpha-2 code (2 letters, case-insensitive).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 60 requests per minute.",
                  "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 · Example",
              "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": "ISO-639-1 locale. Returns a flat `name` in that locale instead of the full `names` map.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "code",
                      "value": "CO",
                      "description": "ISO 3166-1 alpha-2 country code (`CO`, `MX`, `US`...). Case-insensitive."
                    }
                  ]
                },
                "description": {
                  "content": "One country by its ISO 3166-1 alpha-2 code.\n\nReturns one country by its ISO 3166-1 alpha-2 code (2 letters, case-insensitive).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: Any valid API key.\n- Limit: 60 requests per minute.",
                  "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": "URL Shortener",
      "description": {
        "content": "Create and manage short links on your domains, and read their click analytics.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Create a short link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"Create a short link\\\" was skipped because it has effects (creates, changes, deletes, sends, calls or charges). Set confirmoEfectos to si in the environment to send it.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([201]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Optional idempotency key (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the creation with the same key and the same body, you get the link that was already created, without creating another one or charging again. See the [idempotency guide](https://developers.hablame.co/en/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": "Publish a short link with a generated code or your own alias. Charged on creation, unless your plan includes it.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nCreates a short link on one of the domains available to your organization. If you do not send `code`, the service generates a 7-character one (`0-9a-z`); if you do, it becomes an alias (`isAlias: true`). Codes are unique **per domain**, and the code of a deleted link cannot be used for another link: if it was yours, you can [restore it](https://developers.hablame.co/en/docs/reference/urlshortener/restore) for 90 days.\n\nEach created link is charged at your organization’s current price (see [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/en/docs/reference/urlshortener/pricing)). If your organization has an active monthly URL shortener plan, creating links is not charged, and a **daily creation quota** applies instead (100,000 per Colombia day, unless you agreed another one): once reached, the API returns `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` until midnight. Without a plan there is no daily quota: without available balance the API returns `402` and the link is not created.\n\n> **Retry without duplicates with `Idempotency-Key`.** If a creation is cut off (a timeout, a `5xx`) and you repeat it with the same key and the same body, you get the same link (`201` with `Idempotency-Status: replayed`) without creating another one or charging twice, even while the first one is still being processed. The same key with a different body returns `422 IDEMPOTENCY_KEY_REUSED`. On this endpoint the key stays tied to the link forever. Without a key, each retry creates and charges another link.\n\n> The body is validated **before** the balance is checked: an invalid URL, alias, domain or expiry returns its error without touching your balance.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\nMany links at once, for example for a campaign? Use [bulk creation](https://developers.hablame.co/en/docs/reference/urlshortener/batch): up to 1,000 per request, with the same rules and a single charge for what was created.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The destination URL allows up to 2048 characters; the body, up to 64 KiB.\n- Destinations on `localhost`, `*.local`, `*.internal` or on private or reserved IPs are rejected; so are destinations that already are a short link (our domains or shorteners such as bit.ly, tinyurl.com or t.co); destinations with a user or password before the domain (`user@`); domains with accents, ñ or other alphabets (also as `xn--`), and URLs with invisible characters.\n- With a plan: up to 100,000 creations per Colombia day (daily quota).\n- Supports `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "201 Created · Example",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Optional idempotency key (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the creation with the same key and the same body, you get the link that was already created, without creating another one or charging again. See the [idempotency guide](https://developers.hablame.co/en/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": "Publish a short link with a generated code or your own alias. Charged on creation, unless your plan includes it.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nCreates a short link on one of the domains available to your organization. If you do not send `code`, the service generates a 7-character one (`0-9a-z`); if you do, it becomes an alias (`isAlias: true`). Codes are unique **per domain**, and the code of a deleted link cannot be used for another link: if it was yours, you can [restore it](https://developers.hablame.co/en/docs/reference/urlshortener/restore) for 90 days.\n\nEach created link is charged at your organization’s current price (see [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/en/docs/reference/urlshortener/pricing)). If your organization has an active monthly URL shortener plan, creating links is not charged, and a **daily creation quota** applies instead (100,000 per Colombia day, unless you agreed another one): once reached, the API returns `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` until midnight. Without a plan there is no daily quota: without available balance the API returns `402` and the link is not created.\n\n> **Retry without duplicates with `Idempotency-Key`.** If a creation is cut off (a timeout, a `5xx`) and you repeat it with the same key and the same body, you get the same link (`201` with `Idempotency-Status: replayed`) without creating another one or charging twice, even while the first one is still being processed. The same key with a different body returns `422 IDEMPOTENCY_KEY_REUSED`. On this endpoint the key stays tied to the link forever. Without a key, each retry creates and charges another link.\n\n> The body is validated **before** the balance is checked: an invalid URL, alias, domain or expiry returns its error without touching your balance.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\nMany links at once, for example for a campaign? Use [bulk creation](https://developers.hablame.co/en/docs/reference/urlshortener/batch): up to 1,000 per request, with the same rules and a single charge for what was created.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The destination URL allows up to 2048 characters; the body, up to 64 KiB.\n- Destinations on `localhost`, `*.local`, `*.internal` or on private or reserved IPs are rejected; so are destinations that already are a short link (our domains or shorteners such as bit.ly, tinyurl.com or t.co); destinations with a user or password before the domain (`user@`); domains with accents, ñ or other alphabets (also as `xn--`), and URLs with invisible characters.\n- With a plan: up to 100,000 creations per Colombia day (daily quota).\n- Supports `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": "Create short links in bulk",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"Create short links in bulk\\\" was skipped because it has effects (creates, changes, deletes, sends, calls or charges). Set confirmoEfectos to si in the environment to send it.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Optional idempotency key for the **batch** (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the batch with the same key and the same body, you get the links that were already created (`replayed`) without creating or charging them again, and whatever was not created the first time is tried again. With a different body, `422 IDEMPOTENCY_KEY_REUSED`. It is independent from the single-link creation keys. See the [idempotency guide](https://developers.hablame.co/en/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": "Up to 1,000 links in one request, with one result per link and a single charge for what was created.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nCreates up to **1,000 links** in one request. Each link in the batch takes the same four fields as [creating a link](https://developers.hablame.co/en/docs/reference/urlshortener/create) and goes through exactly the same checks: destination, alias, domain and expiry.\n\n**An invalid link does not sink the batch**: the response is `200` with one result per link, in the order of the body (`created`, `replayed` or `failed`), and each failed one carries in `error.code` the same code that creating that link alone would return. What does belong to the whole batch is answered without creating anything: an invalid body or one with more than 1,000 links (`400`), the key used with another body (`422`), the balance or a spend limit (`402`), a missing price or currency (`409`) and the usage limits (`429`).\n\n**A single charge for what was created.** The price of all the valid links is reserved from your balance at once, and only the links actually created are charged: if 998 out of 1,000 are created, 998 are charged. Without balance for all of them, `402` and none is created; send a smaller batch. With an active monthly URL shortener plan there is no charge, and the batch links count against the **daily creation quota**: a batch that does not fit whole is rejected whole with `429 URLSHORTENER_LINK_QUOTA_EXCEEDED`, and the message says how many you have left today.\n\n> **Retry without duplicates with `Idempotency-Key`.** The key belongs to the batch: if a request is cut off and you repeat it with the same key and the same body, the links already created come back as `replayed` (the same ones, without creating others or charging twice, even while the first one is still being processed) and the failed ones are tried again. **What was already created is never hidden**: if what was missing runs into the balance, a limit or a failure on our side, the response is still `200` with the `replayed` links, and each pending link comes back `failed` with that rejection’s code. With the key and a different body, `422 IDEMPOTENCY_KEY_REUSED`. Without a key, each retry creates and charges another batch.\n\n> **The limit counts links, not requests.** Besides the request quota, which it shares with single-link creation, your organization can create up to **2,100 links per minute** in batches (35 per second, the same pace as single creation). Two batches of 1,000 fit back to back; a batch that does not fit is rejected whole with `429 RATE_TPS_EXCEEDED` and `Retry-After`, without creating or charging anything. Single-link creation does not spend this limit.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The 35 requests per second are the same as single-link creation: both routes spend a single quota.\n- Additionally, up to 2,100 links per minute per organization across all its batches. A batch that does not fit is rejected whole.\n- Up to 1,000 links per request; the body, up to 3 MiB; each destination, up to 2048 characters.\n- With a plan: the batch links count against the daily creation quota (100,000 per Colombia day).\n- Supports `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Optional idempotency key for the **batch** (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the batch with the same key and the same body, you get the links that were already created (`replayed`) without creating or charging them again, and whatever was not created the first time is tried again. With a different body, `422 IDEMPOTENCY_KEY_REUSED`. It is independent from the single-link creation keys. See the [idempotency guide](https://developers.hablame.co/en/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": "Up to 1,000 links in one request, with one result per link and a single charge for what was created.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nCreates up to **1,000 links** in one request. Each link in the batch takes the same four fields as [creating a link](https://developers.hablame.co/en/docs/reference/urlshortener/create) and goes through exactly the same checks: destination, alias, domain and expiry.\n\n**An invalid link does not sink the batch**: the response is `200` with one result per link, in the order of the body (`created`, `replayed` or `failed`), and each failed one carries in `error.code` the same code that creating that link alone would return. What does belong to the whole batch is answered without creating anything: an invalid body or one with more than 1,000 links (`400`), the key used with another body (`422`), the balance or a spend limit (`402`), a missing price or currency (`409`) and the usage limits (`429`).\n\n**A single charge for what was created.** The price of all the valid links is reserved from your balance at once, and only the links actually created are charged: if 998 out of 1,000 are created, 998 are charged. Without balance for all of them, `402` and none is created; send a smaller batch. With an active monthly URL shortener plan there is no charge, and the batch links count against the **daily creation quota**: a batch that does not fit whole is rejected whole with `429 URLSHORTENER_LINK_QUOTA_EXCEEDED`, and the message says how many you have left today.\n\n> **Retry without duplicates with `Idempotency-Key`.** The key belongs to the batch: if a request is cut off and you repeat it with the same key and the same body, the links already created come back as `replayed` (the same ones, without creating others or charging twice, even while the first one is still being processed) and the failed ones are tried again. **What was already created is never hidden**: if what was missing runs into the balance, a limit or a failure on our side, the response is still `200` with the `replayed` links, and each pending link comes back `failed` with that rejection’s code. With the key and a different body, `422 IDEMPOTENCY_KEY_REUSED`. Without a key, each retry creates and charges another batch.\n\n> **The limit counts links, not requests.** Besides the request quota, which it shares with single-link creation, your organization can create up to **2,100 links per minute** in batches (35 per second, the same pace as single creation). Two batches of 1,000 fit back to back; a batch that does not fit is rejected whole with `429 RATE_TPS_EXCEEDED` and `Retry-After`, without creating or charging anything. Single-link creation does not spend this limit.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The 35 requests per second are the same as single-link creation: both routes spend a single quota.\n- Additionally, up to 2,100 links per minute per organization across all its batches. A batch that does not fit is rejected whole.\n- Up to 1,000 links per request; the body, up to 3 MiB; each destination, up to 2048 characters.\n- With a plan: the batch links count against the daily creation quota (100,000 per Colombia day).\n- Supports `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": "List short links",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "1-based page number. It reaches up to link 10,000: the page must start, at the latest, at link 10,000. Ignored with `cursor`.",
                  "disabled": true
                },
                {
                  "key": "perPage",
                  "value": "20",
                  "description": "Items per page (max 100).",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "The `nextCursor` of the previous response, as is. The page starts right after the last link of that response. It must go with the same `sort` and `order`; a cursor that is not from this organization or this order returns `400`.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Searches the code and the destination URL, by fragment and ignoring case and accents (minimum 2 characters; shorter is ignored). If you paste a full short URL (`h0b.co/a1b2c3d` or `https://h0b.co/a1b2c3d`), it returns exactly that link, also if its domain no longer belongs to your organization (`domainStatus: released`).",
                  "disabled": true
                },
                {
                  "key": "domain",
                  "value": "",
                  "description": "Only links on this domain. Those on a domain that no longer belongs to your organization (`domainStatus: released`) are not included even if they have the same name: to find one, paste its short URL in `search`.",
                  "disabled": true
                },
                {
                  "key": "state",
                  "value": "",
                  "description": "Status as a visitor experiences it: `active` (active and not expired), `expired` (active with its expiry already past: it no longer redirects), `disabled` or `blocked` (blocked by Hablame).",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "description": "Stored status. `active` includes expired links. Kept for compatibility; prefer `state`.",
                  "disabled": true
                },
                {
                  "key": "kind",
                  "value": "",
                  "description": "Links with a chosen alias, or with a generated code.",
                  "disabled": true
                },
                {
                  "key": "traffic",
                  "value": "",
                  "description": "`none`: links that never received a visit. `some`: links that received at least one.",
                  "disabled": true
                },
                {
                  "key": "createdFrom",
                  "value": "2026-09-01",
                  "description": "Created from this day on (`YYYY-MM-DD`, Colombia time), inclusive.",
                  "disabled": true
                },
                {
                  "key": "createdTo",
                  "value": "2026-09-23",
                  "description": "Created up to this day (`YYYY-MM-DD`, Colombia time), inclusive.",
                  "disabled": true
                },
                {
                  "key": "sort",
                  "value": "created",
                  "description": "Order: by creation date, by lifetime visits, by last visit or by expiry. With `lastClick` and `expires`, links without that value go last in both directions.",
                  "disabled": true
                },
                {
                  "key": "order",
                  "value": "desc",
                  "description": "Sort direction.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Your links, paginated, with search, filters, sorting and their visit counters.\n\nLists your organization’s links (never deleted ones) with their counters already consolidated. By default, newest first. Every filter is optional and they combine with each other.\n\n> `status` keeps its original meaning: `active` **includes expired links**, because expiring does not change the stored status. To separate current links from expired ones use `state`.\n\n**To walk through many links, paginate with `cursor`.** Each response carries `nextCursor` while there are more: pass it in the next request, with the same filters, `sort` and `order`, and the page starts right after the last link of the previous one. `page` keeps working as always, but only up to link 10,000 (with `perPage=100`, up to page 100); beyond that it returns `400` and you continue with `cursor`, which has no cap.\n\n**`total` is exact up to 10,000.** If more links match the filters, `total` comes as `10000` and `totalIsExact` as `false`: “more than 10,000”. `hasMore` always tells, without error, whether there is another page.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 30 requests per minute.\n- Up to 100 items per page; a larger value is capped at 100.\n- `total` is exact up to 10,000; above that, `totalIsExact` is `false`.\n- With `page` you reach up to link 10,000; to go further, `cursor`.\n- A search or a sort too expensive for a very large organization is stopped after a few seconds with `504`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "1-based page number. It reaches up to link 10,000: the page must start, at the latest, at link 10,000. Ignored with `cursor`.",
                      "disabled": true
                    },
                    {
                      "key": "perPage",
                      "value": "20",
                      "description": "Items per page (max 100).",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "The `nextCursor` of the previous response, as is. The page starts right after the last link of that response. It must go with the same `sort` and `order`; a cursor that is not from this organization or this order returns `400`.",
                      "disabled": true
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "Searches the code and the destination URL, by fragment and ignoring case and accents (minimum 2 characters; shorter is ignored). If you paste a full short URL (`h0b.co/a1b2c3d` or `https://h0b.co/a1b2c3d`), it returns exactly that link, also if its domain no longer belongs to your organization (`domainStatus: released`).",
                      "disabled": true
                    },
                    {
                      "key": "domain",
                      "value": "",
                      "description": "Only links on this domain. Those on a domain that no longer belongs to your organization (`domainStatus: released`) are not included even if they have the same name: to find one, paste its short URL in `search`.",
                      "disabled": true
                    },
                    {
                      "key": "state",
                      "value": "",
                      "description": "Status as a visitor experiences it: `active` (active and not expired), `expired` (active with its expiry already past: it no longer redirects), `disabled` or `blocked` (blocked by Hablame).",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "Stored status. `active` includes expired links. Kept for compatibility; prefer `state`.",
                      "disabled": true
                    },
                    {
                      "key": "kind",
                      "value": "",
                      "description": "Links with a chosen alias, or with a generated code.",
                      "disabled": true
                    },
                    {
                      "key": "traffic",
                      "value": "",
                      "description": "`none`: links that never received a visit. `some`: links that received at least one.",
                      "disabled": true
                    },
                    {
                      "key": "createdFrom",
                      "value": "2026-09-01",
                      "description": "Created from this day on (`YYYY-MM-DD`, Colombia time), inclusive.",
                      "disabled": true
                    },
                    {
                      "key": "createdTo",
                      "value": "2026-09-23",
                      "description": "Created up to this day (`YYYY-MM-DD`, Colombia time), inclusive.",
                      "disabled": true
                    },
                    {
                      "key": "sort",
                      "value": "created",
                      "description": "Order: by creation date, by lifetime visits, by last visit or by expiry. With `lastClick` and `expires`, links without that value go last in both directions.",
                      "disabled": true
                    },
                    {
                      "key": "order",
                      "value": "desc",
                      "description": "Sort direction.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Your links, paginated, with search, filters, sorting and their visit counters.\n\nLists your organization’s links (never deleted ones) with their counters already consolidated. By default, newest first. Every filter is optional and they combine with each other.\n\n> `status` keeps its original meaning: `active` **includes expired links**, because expiring does not change the stored status. To separate current links from expired ones use `state`.\n\n**To walk through many links, paginate with `cursor`.** Each response carries `nextCursor` while there are more: pass it in the next request, with the same filters, `sort` and `order`, and the page starts right after the last link of the previous one. `page` keeps working as always, but only up to link 10,000 (with `perPage=100`, up to page 100); beyond that it returns `400` and you continue with `cursor`, which has no cap.\n\n**`total` is exact up to 10,000.** If more links match the filters, `total` comes as `10000` and `totalIsExact` as `false`: “more than 10,000”. `hasMore` always tells, without error, whether there is another page.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 30 requests per minute.\n- Up to 100 items per page; a larger value is capped at 100.\n- `total` is exact up to 10,000; above that, `totalIsExact` is `false`.\n- With `page` you reach up to link 10,000; to go further, `cursor`.\n- A search or a sort too expensive for a very large organization is stopped after a few seconds with `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": "List available domains",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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` adds your own domains that are not verified yet, to see them; they cannot be used to create links.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Domains your organization can publish links on, with the default first.\n\nLists the active domains your organization can publish links on: platform domains plus your own already verified ones, with the default first and the rest in alphabetical order. An own domain that is not verified yet cannot be used to create links and does not appear, unless you ask for it with `include=unverified` (it comes with `verified: false`). There is no endpoint to register or verify your own domains.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 30 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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` adds your own domains that are not verified yet, to see them; they cannot be used to create links.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Domains your organization can publish links on, with the default first.\n\nLists the active domains your organization can publish links on: platform domains plus your own already verified ones, with the default first and the rest in alphabetical order. An own domain that is not verified yet cannot be used to create links and does not appear, unless you ask for it with `include=unverified` (it comes with `verified: false`). There is no endpoint to register or verify your own domains.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 30 requests per minute.",
                  "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": "Get a short link",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "One of your links by domain and code.\n\nReturns one link of your organization with its lifetime counters. A deleted one, or one from another organization, returns `404`. One blocked by Hablame comes back with `status: blocked`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "One of your links by domain and code.\n\nReturns one link of your organization with its lifetime counters. A deleted one, or one from another organization, returns `404`. One blocked by Hablame comes back with `status: blocked`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
                  "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": "Update a short link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"Update a short link\\\" was skipped because it has effects (creates, changes, deletes, sends, calls or charges). Set confirmoEfectos to si in the environment to send it.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"disabled\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": {
              "content": "Change the destination, the status or the expiry. Domain and code do not change.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nChanges the destination URL (`longUrl`, revalidated as on creation), the status (`status`) or the expiry (`expiresAt`). Send only what you want to change; at least one field is required. Domain and code cannot change: that would break every short URL already handed out. Editing is free.\n\nEvery change is recorded with the previous value, the new one and who made it (the API key or the portal user). A link blocked by Hablame (`status: blocked`) cannot be edited: it returns `409 LINK_BLOCKED`.\n\n> Keys it does not recognize **are ignored**: a body like `{\"active\": false}` (from the previous contract) changes nothing and returns `400 NOTHING_TO_UPDATE`. To disable, send `{\"status\": \"disabled\"}`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Changes data in your organization.\n- Charge: The call is not charged.\n- If this change makes the link redirect again (you reactivate it, or extend or remove its expiry), every visit by a person is charged again (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"status\": \"disabled\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "description": {
                  "content": "Change the destination, the status or the expiry. Domain and code do not change.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nChanges the destination URL (`longUrl`, revalidated as on creation), the status (`status`) or the expiry (`expiresAt`). Send only what you want to change; at least one field is required. Domain and code cannot change: that would break every short URL already handed out. Editing is free.\n\nEvery change is recorded with the previous value, the new one and who made it (the API key or the portal user). A link blocked by Hablame (`status: blocked`) cannot be edited: it returns `409 LINK_BLOCKED`.\n\n> Keys it does not recognize **are ignored**: a body like `{\"active\": false}` (from the previous contract) changes nothing and returns `400 NOTHING_TO_UPDATE`. To disable, send `{\"status\": \"disabled\"}`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Changes data in your organization.\n- Charge: The call is not charged.\n- If this change makes the link redirect again (you reactivate it, or extend or remove its expiry), every visit by a person is charged again (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
                  "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": "Delete a short link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"Delete a short link\\\" was skipped because it has effects (creates, changes, deletes, sends, calls or charges). Set confirmoEfectos to si in the environment to send it.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([204]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"It has no body\", 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "The link stops redirecting and disappears from the API. Its code stays taken, and you can restore it for 90 days.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nA **soft** delete: the link stops redirecting right away (the visitor gets `404`) and disappears from every API response, statistics included. What was charged is kept, and who deleted it is recorded. Its code stays taken forever on that domain: nobody can create another link with it, but you can [restore this one](https://developers.hablame.co/en/docs/reference/urlshortener/restore), with no new charge, **for 90 days**. After that the link is archived with its visits and can no longer be restored; the code stays taken.\n\nA link blocked by Hablame (`status: blocked`) cannot be deleted: it returns `409 LINK_BLOCKED`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Deletes data in your organization.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "204 No Content · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "The link stops redirecting and disappears from the API. Its code stays taken, and you can restore it for 90 days.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nA **soft** delete: the link stops redirecting right away (the visitor gets `404`) and disappears from every API response, statistics included. What was charged is kept, and who deleted it is recorded. Its code stays taken forever on that domain: nobody can create another link with it, but you can [restore this one](https://developers.hablame.co/en/docs/reference/urlshortener/restore), with no new charge, **for 90 days**. After that the link is archived with its visits and can no longer be restored; the code stays taken.\n\nA link blocked by Hablame (`status: blocked`) cannot be deleted: it returns `409 LINK_BLOCKED`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Deletes data in your organization.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
                  "type": "text/markdown"
                }
              },
              "status": "No Content",
              "code": 204,
              "_postman_previewlanguage": "text",
              "header": [],
              "cookie": [],
              "body": ""
            }
          ]
        },
        {
          "name": "Restore a deleted link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoEfectos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"Restore a deleted link\\\" was skipped because it has effects (creates, changes, deletes, sends, calls or charges). Set confirmoEfectos to si in the environment to send it.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "Brings back a link of yours that you deleted, with the same code and no new charge.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nThe code of a deleted link is never released, so you cannot create it again (creating with that alias returns `409 ALIAS_RETIRED`). With this endpoint you restore it: it comes back with its destination, its expiry and **the status it had when you deleted it** (if it was disabled, it comes back disabled), and its earlier visits keep counting. Restoring it is not charged; if it comes back active, its later visits are, like those of any active link. You can do it **for 90 days** after deleting it: after that the link is archived and restoring it returns `404 LINK_NOT_FOUND`.\n\n- Restoring a link that is not deleted returns it as is: you can retry safely.\n- A link blocked by Hablame cannot be restored (`409 LINK_BLOCKED`).\n- Who restored it is recorded. It shares the usage limit of editing.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Changes data in your organization.\n- Charge: The call is not charged.\n- If the link was active when you deleted it, it comes back active and every visit by a person is charged again (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "Brings back a link of yours that you deleted, with the same code and no new charge.\n\n**This request has effects and does not send itself.** To send it, set `confirmoEfectos` to `si` in the environment.\n\nThe code of a deleted link is never released, so you cannot create it again (creating with that alias returns `409 ALIAS_RETIRED`). With this endpoint you restore it: it comes back with its destination, its expiry and **the status it had when you deleted it** (if it was disabled, it comes back disabled), and its earlier visits keep counting. Restoring it is not charged; if it comes back active, its later visits are, like those of any active link. You can do it **for 90 days** after deleting it: after that the link is archived and restoring it returns `404 LINK_NOT_FOUND`.\n\n- Restoring a link that is not deleted returns it as is: you can retry safely.\n- A link blocked by Hablame cannot be restored (`409 LINK_BLOCKED`).\n- Who restored it is recorded. It shares the usage limit of editing.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Changes data in your organization.\n- Charge: The call is not charged.\n- If the link was active when you deleted it, it comes back active and every visit by a person is charged again (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
                  "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": "Link statistics",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-09-01",
                  "description": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "How many values each dimension (and each ranking) returns. Max 100.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "h0b.co",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "a1b2c3d",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "Range totals, a daily or hourly series and breakdowns by country, device, OS, browser or source.\n\nAnalytics for one link over a window of days: the range totals, a daily or hourly series and the values with the most visits for each dimension. Days and hours are Colombia’s (`America/Bogota`).\n\n- `totals.clicks` and `totals.uniqueClicks` are the range’s; `firstClickAt` and `lastClickAt`, the link’s lifetime.\n- The series is **sparse**: days or hours without visits are omitted. Fill them with zero if you are going to chart it.\n- Each dimension returns the `top` values with the most visits and, if there is a remainder, an `__other` row. A visit with no value for a dimension (no country, or no source) does not appear in it: its rows can add up to less than the total.\n- `referrer` is only the source domain (`www.facebook.com`), without the path. Direct visits have no row: they are `totals.clicks` minus the sum of `referrer`.\n- `device` is `mobile`, `tablet` or `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` or `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` or `Other`. `country` is the ISO code (`CO`).\n\n> `uniqueClicks` counts the first visit of the day from each browser. Over a multi-day range it is the **sum** of each day, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension.\n- On top of that, each query spends units from a quota of 120 per minute for your organization, according to its cost: 2 for the query and 3 for every 31 days of the range. 30 days spend 5; a year, 38. Without units, `429` with `Retry-After`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-09-01",
                      "description": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "How many values each dimension (and each ranking) returns. Max 100.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "h0b.co",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "a1b2c3d",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "Range totals, a daily or hourly series and breakdowns by country, device, OS, browser or source.\n\nAnalytics for one link over a window of days: the range totals, a daily or hourly series and the values with the most visits for each dimension. Days and hours are Colombia’s (`America/Bogota`).\n\n- `totals.clicks` and `totals.uniqueClicks` are the range’s; `firstClickAt` and `lastClickAt`, the link’s lifetime.\n- The series is **sparse**: days or hours without visits are omitted. Fill them with zero if you are going to chart it.\n- Each dimension returns the `top` values with the most visits and, if there is a remainder, an `__other` row. A visit with no value for a dimension (no country, or no source) does not appear in it: its rows can add up to less than the total.\n- `referrer` is only the source domain (`www.facebook.com`), without the path. Direct visits have no row: they are `totals.clicks` minus the sum of `referrer`.\n- `device` is `mobile`, `tablet` or `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` or `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` or `Other`. `country` is the ISO code (`CO`).\n\n> `uniqueClicks` counts the first visit of the day from each browser. Over a multi-day range it is the **sum** of each day, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension.\n- On top of that, each query spends units from a quota of 120 per minute for your organization, according to its cost: 2 for the query and 3 for every 31 days of the range. 30 days spend 5; a year, 38. Without units, `429` with `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": "Organization overview",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                  "disabled": true
                },
                {
                  "key": "compare",
                  "value": "none",
                  "description": "`previous` adds the previous period of the same length (`previous`, `previousTotals` and, with `series`, `previousSeries`). `none`, or not sending it, omits it. Until 2026-09-30 the default was `previous`.",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "series,top,recent,dimensions,heatmap",
                  "description": "Comma-separated optional blocks: `series`, `top`, `recent`, `dimensions`, `heatmap`. Defaults to `series,top,recent`. An unknown name returns `400`.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "How many values each dimension (and each ranking) returns. Max 100.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Inventory, range activity against the previous period, trends, top links and breakdowns for your whole organization.\n\nYour organization’s whole URL shortener in a single call, without walking the links one by one. Days and hours are Colombia’s (`America/Bogota`).\n\n- `inventory` is a snapshot of the moment and **does not depend on the range**: live links by status (`expired` are active ones whose expiry has passed), aliases, those that never received a visit and lifetime visits.\n- `totals` is what happened in the range, and `today`, the same for today. **The previous period is requested with `compare=previous`**: then `previous`, `previousTotals` and, with `series`, `previousSeries` come, for the previous period of the same length (30 days up to today are compared with the 30 before, not with the calendar month). Without asking for it they come as `null`.\n- Optional blocks are requested in `include`. Those you do not request come as `null`; those you request that have no data, empty.\n\n> Unique visits are the sum of each day’s, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n> **Change of 2026-09-30:** the previous period used to come always; now only with `compare=previous`. If you use it, add the parameter.\n\nThe overview walks every link of your organization, so its quota goes **by what it costs**: each overview spends units from a quota of 150 per minute for your organization. It spends 2 for the query; 1 for each block that does not depend on the length of the range (`inventory`, `today`, `recent`, `heatmap`), and, for every 31 days of the range, 1 for each block that walks it (`totals`, `series`, `top`, `dimensions`, and the previous-period ones if you compare). A 30-day overview with `series,top,recent` spends 8; comparing, 10; a one-year one with everything, 78. Without units, `429` with `Retry-After`. And at most two overviews of your organization are computed at once: one arriving with two in progress waits a few seconds and, if it does not get a turn, gets `429`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension and per ranking.\n- Cost quota: 150 units per minute per organization (see above). Two overviews at once per organization.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                      "disabled": true
                    },
                    {
                      "key": "compare",
                      "value": "none",
                      "description": "`previous` adds the previous period of the same length (`previous`, `previousTotals` and, with `series`, `previousSeries`). `none`, or not sending it, omits it. Until 2026-09-30 the default was `previous`.",
                      "disabled": true
                    },
                    {
                      "key": "include",
                      "value": "series,top,recent,dimensions,heatmap",
                      "description": "Comma-separated optional blocks: `series`, `top`, `recent`, `dimensions`, `heatmap`. Defaults to `series,top,recent`. An unknown name returns `400`.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "How many values each dimension (and each ranking) returns. Max 100.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Inventory, range activity against the previous period, trends, top links and breakdowns for your whole organization.\n\nYour organization’s whole URL shortener in a single call, without walking the links one by one. Days and hours are Colombia’s (`America/Bogota`).\n\n- `inventory` is a snapshot of the moment and **does not depend on the range**: live links by status (`expired` are active ones whose expiry has passed), aliases, those that never received a visit and lifetime visits.\n- `totals` is what happened in the range, and `today`, the same for today. **The previous period is requested with `compare=previous`**: then `previous`, `previousTotals` and, with `series`, `previousSeries` come, for the previous period of the same length (30 days up to today are compared with the 30 before, not with the calendar month). Without asking for it they come as `null`.\n- Optional blocks are requested in `include`. Those you do not request come as `null`; those you request that have no data, empty.\n\n> Unique visits are the sum of each day’s, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n> **Change of 2026-09-30:** the previous period used to come always; now only with `compare=previous`. If you use it, add the parameter.\n\nThe overview walks every link of your organization, so its quota goes **by what it costs**: each overview spends units from a quota of 150 per minute for your organization. It spends 2 for the query; 1 for each block that does not depend on the length of the range (`inventory`, `today`, `recent`, `heatmap`), and, for every 31 days of the range, 1 for each block that walks it (`totals`, `series`, `top`, `dimensions`, and the previous-period ones if you compare). A 30-day overview with `series,top,recent` spends 8; comparing, 10; a one-year one with everything, 78. Without units, `429` with `Retry-After`. And at most two overviews of your organization are computed at once: one arriving with two in progress waits a few seconds and, if it does not get a turn, gets `429`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension and per ranking.\n- Cost quota: 150 units per minute per organization (see above). Two overviews at once per organization.",
                  "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": "URL Shortener pricing",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "What creating a link and each visit cost your organization, and whether its plan includes it.\n\nReturns the prices applied to your organization today: the service’s general ones or the ones agreed for it. Each link creation is charged, and so is **every visit by a person** that reaches the destination, repeat visits included. Visits from bots and the previews that messaging apps (WhatsApp, Telegram, Facebook, Google Messages and the like) build when someone shares the link are not charged.\n\nAmounts are in **micros**: currency units multiplied by 10⁶ (`1000000` is 1.00). `includedInPlan: true` means your organization has an active monthly URL shortener plan today: link creation and visits are included. Prices come anyway, so you know what the plan covers; if no price is configured, they come as `null`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "What creating a link and each visit cost your organization, and whether its plan includes it.\n\nReturns the prices applied to your organization today: the service’s general ones or the ones agreed for it. Each link creation is charged, and so is **every visit by a person** that reaches the destination, repeat visits included. Visits from bots and the previews that messaging apps (WhatsApp, Telegram, Facebook, Google Messages and the like) build when someone shares the link are not charged.\n\nAmounts are in **micros**: currency units multiplied by 10⁶ (`1000000` is 1.00). `includedInPlan: true` means your organization has an active monthly URL shortener plan today: link creation and visits are included. Prices come anyway, so you know what the plan covers; if no price is configured, they come as `null`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
                  "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": "Storage",
      "description": {
        "content": "Read what your organization stores: usage, consumption, files, download links, retention and pricing. Read-only; storage never pauses.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Summary",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "What your organization has stored now, the month so far and what is deleted soon.\n\nReturns what your files take up now (in total, per service and per file type), the price in force today, the usage of the current month and how many files are deleted in the next 24 hours and 7 days.\n\n`month.costMicro` is what is already consolidated up to `month.through`; `month.estimatedTodayMicro` estimates what is still to be consolidated (normally today). `price` comes as `null` if your organization does not have a final currency yet.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "What your organization has stored now, the month so far and what is deleted soon.\n\nReturns what your files take up now (in total, per service and per file type), the price in force today, the usage of the current month and how many files are deleted in the next 24 hours and 7 days.\n\n`month.costMicro` is what is already consolidated up to `month.through`; `month.estimatedTodayMicro` estimates what is still to be consolidated (normally today). `price` comes as `null` if your organization does not have a final currency yet.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
                  "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": "Usage",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "How the series is grouped.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-10-01",
                  "description": "Start of the range (included).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-10-19",
                  "description": "End of the range (excluded).",
                  "disabled": true
                },
                {
                  "key": "service",
                  "value": "callblasting",
                  "description": "Code of the service that stored the files (the `service` of the responses). An unknown code answers `400 INVALID_FILTER`.",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "recordings",
                  "description": "Code of the file type within the service (the `scope` of the responses). Requires `service`.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Series of the storage used (GB-months and cost) per day, month or hour.\n\nReturns a series with what you used in each period, the total of the range and the one of the previous range of the same length, to compare. Per day or month, `from` and `to` are `YYYY-MM-DD` dates (Colombia time, `to` excluded) and the range is up to 400 days; without them, the current month. Per hour, they are ISO-8601 instants (without a zone they are read in Colombia time), the range is up to 7 days and the cost of each point comes as `null`: cost is computed per day.\n\n`byteSeconds` is the exact storage (bytes times seconds stored) and comes as a string because it can exceed the largest integer JSON represents without losing precision.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "How the series is grouped.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-10-01",
                      "description": "Start of the range (included).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-10-19",
                      "description": "End of the range (excluded).",
                      "disabled": true
                    },
                    {
                      "key": "service",
                      "value": "callblasting",
                      "description": "Code of the service that stored the files (the `service` of the responses). An unknown code answers `400 INVALID_FILTER`.",
                      "disabled": true
                    },
                    {
                      "key": "scope",
                      "value": "recordings",
                      "description": "Code of the file type within the service (the `scope` of the responses). Requires `service`.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Series of the storage used (GB-months and cost) per day, month or hour.\n\nReturns a series with what you used in each period, the total of the range and the one of the previous range of the same length, to compare. Per day or month, `from` and `to` are `YYYY-MM-DD` dates (Colombia time, `to` excluded) and the range is up to 400 days; without them, the current month. Per hour, they are ISO-8601 instants (without a zone they are read in Colombia time), the range is up to 7 days and the cost of each point comes as `null`: cost is computed per day.\n\n`byteSeconds` is the exact storage (bytes times seconds stored) and comes as a string because it can exceed the largest integer JSON represents without losing precision.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
                  "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": "List files",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Code of the service that stored the files (the `service` of the responses). An unknown code answers `400 INVALID_FILTER`.",
                  "disabled": true
                },
                {
                  "key": "scope",
                  "value": "recordings",
                  "description": "Code of the file type within the service (the `scope` of the responses). Requires `service`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "",
                  "description": "Only those stored from this instant (ISO-8601; without a zone, Colombia time).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "",
                  "description": "Only those stored before this instant.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Files per page.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "The `next` of the previous page.",
                  "disabled": true
                }
              ]
            },
            "description": {
              "content": "Your organization’s files, newest first, paginated by cursor.\n\nLists the stored files and the ones being deleted, newest first. It paginates by cursor: if `next` is not `null`, pass it as `cursor` for the next page **with the same filters**. A cursor that was altered, belongs to another organization, uses other filters or is more than 24 hours old answers `400 INVALID_CURSOR`: go back to the first page.\n\n> **Files require a key with Storage** Files may be call recordings, audio or documents of your organization. Any API key with the Storage service (`storage`) can see them, and only the owner of your organization creates keys. Without the service, the key gets `403 AUTH_SERVICE_NOT_ALLOWED` here and on every other Storage endpoint.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "Code of the service that stored the files (the `service` of the responses). An unknown code answers `400 INVALID_FILTER`.",
                      "disabled": true
                    },
                    {
                      "key": "scope",
                      "value": "recordings",
                      "description": "Code of the file type within the service (the `scope` of the responses). Requires `service`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "",
                      "description": "Only those stored from this instant (ISO-8601; without a zone, Colombia time).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "",
                      "description": "Only those stored before this instant.",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "20",
                      "description": "Files per page.",
                      "disabled": true
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "The `next` of the previous page.",
                      "disabled": true
                    }
                  ]
                },
                "description": {
                  "content": "Your organization’s files, newest first, paginated by cursor.\n\nLists the stored files and the ones being deleted, newest first. It paginates by cursor: if `next` is not `null`, pass it as `cursor` for the next page **with the same filters**. A cursor that was altered, belongs to another organization, uses other filters or is more than 24 hours old answers `400 INVALID_CURSOR`: go back to the first page.\n\n> **Files require a key with Storage** Files may be call recordings, audio or documents of your organization. Any API key with the Storage service (`storage`) can see them, and only the owner of your organization creates keys. Without the service, the key gets `403 AUTH_SERVICE_NOT_ALLOWED` here and on every other Storage endpoint.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
                  "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": "Get a file",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "File identifier, as it comes in the list."
                }
              ]
            },
            "description": {
              "content": "The detail of a file of your organization.\n\nReturns one file, with the same shape as in the list.\n\n> **Files require a key with Storage** Files may be call recordings, audio or documents of your organization. Any API key with the Storage service (`storage`) can see them, and only the owner of your organization creates keys. Without the service, the key gets `403 AUTH_SERVICE_NOT_ALLOWED` here and on every other Storage endpoint.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": []
        },
        {
          "name": "Download link",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Minutes the link is valid.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "obj_01JABCDEFGHJKMNPQRSTVWXYZ0",
                  "description": "File identifier, as it comes in the list."
                }
              ]
            },
            "description": {
              "content": "A temporary link to download a file.\n\nReturns a signed link to download the file, valid until `expiresAt` (15 minutes by default, 60 at most) and never beyond when the file is deleted. Each link is recorded with the API key that asked for it and the IP you called from. A file being deleted cannot be downloaded: it answers `404`.\n\nThe link is a short-lived secret: do not store or share it. Ask for a new one every time you need it.\n\n> **Files require a key with Storage** Files may be call recordings, audio or documents of your organization. Any API key with the Storage service (`storage`) can see them, and only the owner of your organization creates keys. Without the service, the key gets `403 AUTH_SERVICE_NOT_ALLOWED` here and on every other Storage endpoint.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Trail: Your organization’s audit log records that a download link was requested for that file.\n- Access: API key with the `storage` service enabled.\n- Limit: 2 requests per second.\n- Its own per-organization quota, separate from the queries.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "Minutes the link is valid.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "obj_01JABCDEFGHJKMNPQRSTVWXYZ0",
                      "description": "File identifier, as it comes in the list."
                    }
                  ]
                },
                "description": {
                  "content": "A temporary link to download a file.\n\nReturns a signed link to download the file, valid until `expiresAt` (15 minutes by default, 60 at most) and never beyond when the file is deleted. Each link is recorded with the API key that asked for it and the IP you called from. A file being deleted cannot be downloaded: it answers `404`.\n\nThe link is a short-lived secret: do not store or share it. Ask for a new one every time you need it.\n\n> **Files require a key with Storage** Files may be call recordings, audio or documents of your organization. Any API key with the Storage service (`storage`) can see them, and only the owner of your organization creates keys. Without the service, the key gets `403 AUTH_SERVICE_NOT_ALLOWED` here and on every other Storage endpoint.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Trail: Your organization’s audit log records that a download link was requested for that file.\n- Access: API key with the `storage` service enabled.\n- Limit: 2 requests per second.\n- Its own per-organization quota, separate from the queries.",
                  "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": "Retention",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "How long each file type is kept before it is deleted.\n\nReturns, per file type, the current retention time, the minimum and maximum it allows, the default, whether your organization chose its own and who changed it last. `deletionInProgress.objects` counts the files being deleted now. The time is changed from the customer portal: here it is only read.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "How long each file type is kept before it is deleted.\n\nReturns, per file type, the current retention time, the minimum and maximum it allows, the default, whether your organization chose its own and who changed it last. `deletionInProgress.objects` counts the files being deleted now. The time is changed from the customer portal: here it is only read.\n\n> **Read-only; storage never pauses** Your files are stored continuously and never pause, even without balance. To query them over the API, the API key needs the Storage service (`storage`): with it, the key sees everything (summary, usage, files, downloads, retention and pricing), and without it each of these endpoints answers `403 AUTH_SERVICE_NOT_ALLOWED`. The API is **read-only**: the retention time is changed from the customer portal (Storage → Retention) and a file is deleted there or when it expires. Any method other than `GET` answers `405`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
                  "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": "Storage pricing",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "The price per GB-month applied to your organization today, and the next one if already set.\n\nReturns today’s price in your organization’s currency: the general one (`base`) or an agreed one (`custom`). `upcoming` is the one that will apply from a future date, if already set. If your organization does not have a final currency yet, the price fields come as `null`.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "The price per GB-month applied to your organization today, and the next one if already set.\n\nReturns today’s price in your organization’s currency: the general one (`base`) or an agreed one (`custom`). `upcoming` is the one that will apply from a future date, if already set. If your organization does not have a final currency yet, the price fields come as `null`.\n\nAmounts are in **micros** of your organization’s currency (`currency`, ISO 4217): units multiplied by 10⁶ (`1000000` is 1.00). Storage is billed per **GB-month** (decimal GB, 10⁹ bytes) according to how long each file was stored.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `storage` service enabled.\n- Limit: 10 requests per second.\n- The six Storage queries (summary, usage, list, detail, retention and pricing) share this per-organization quota: it is not a per-endpoint quota.",
                  "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": "Organization",
      "description": {
        "content": "Organization profile, cost centers and users. Includes personal data.",
        "type": "text/markdown"
      },
      "item": [
        {
          "name": "Organization",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Profile of the organization that owns the API key.\n\nProfile of the organization that owns the API key: identity and configuration. It does not include balance or credit.\n\n> **Includes personal data** The API key must have the `directory` capability explicitly enabled. Access to all services does NOT grant it. A key without the capability gets `403`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `directory` capability granted.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "Profile of the organization that owns the API key.\n\nProfile of the organization that owns the API key: identity and configuration. It does not include balance or credit.\n\n> **Includes personal data** The API key must have the `directory` capability explicitly enabled. Access to all services does NOT grant it. A key without the capability gets `403`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `directory` capability granted.\n- Limit: 60 requests per minute.",
                  "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": "Flows: they change data and charge",
      "description": {
        "content": "Chained requests that **create, read and delete** a short link. Everything is real: creating the link is charged and deleting it changes data in your organization. They never run by themselves: they need `confirmoFlujos` set to `si`. If a step fails, the flow stops.\n\nThe steps share the domain and code of the created link through the collection variables `flujoDominio` and `flujoCodigo`.",
        "type": "text/markdown"
      },
      "event": [
        {
          "listen": "prerequest",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
              "  console.log(\"Hablame: \\\"Flows: they change data and charge\\\" was skipped because it belongs to a flow that changes data and charges. Set confirmoFlujos to si in the environment to run the flow.\");",
              "  if (pm.execution && typeof pm.execution.skipRequest === 'function') {",
              "    pm.execution.skipRequest();",
              "  } else {",
              "    pm.request.url.update('https://bloqueado.invalid/');",
              "  }",
              "}"
            ]
          }
        }
      ],
      "item": [
        {
          "name": "1. Create a short link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"1. Create a short link\\\" was skipped because it belongs to a flow that changes data and charges. Set confirmoFlujos to si in the environment to run the flow.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([201]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "Optional idempotency key (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the creation with the same key and the same body, you get the link that was already created, without creating another one or charging again. See the [idempotency guide](https://developers.hablame.co/en/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": "Publish a short link with a generated code or your own alias. Charged on creation, unless your plan includes it.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nCreates a short link on one of the domains available to your organization. If you do not send `code`, the service generates a 7-character one (`0-9a-z`); if you do, it becomes an alias (`isAlias: true`). Codes are unique **per domain**, and the code of a deleted link cannot be used for another link: if it was yours, you can [restore it](https://developers.hablame.co/en/docs/reference/urlshortener/restore) for 90 days.\n\nEach created link is charged at your organization’s current price (see [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/en/docs/reference/urlshortener/pricing)). If your organization has an active monthly URL shortener plan, creating links is not charged, and a **daily creation quota** applies instead (100,000 per Colombia day, unless you agreed another one): once reached, the API returns `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` until midnight. Without a plan there is no daily quota: without available balance the API returns `402` and the link is not created.\n\n> **Retry without duplicates with `Idempotency-Key`.** If a creation is cut off (a timeout, a `5xx`) and you repeat it with the same key and the same body, you get the same link (`201` with `Idempotency-Status: replayed`) without creating another one or charging twice, even while the first one is still being processed. The same key with a different body returns `422 IDEMPOTENCY_KEY_REUSED`. On this endpoint the key stays tied to the link forever. Without a key, each retry creates and charges another link.\n\n> The body is validated **before** the balance is checked: an invalid URL, alias, domain or expiry returns its error without touching your balance.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\nMany links at once, for example for a campaign? Use [bulk creation](https://developers.hablame.co/en/docs/reference/urlshortener/batch): up to 1,000 per request, with the same rules and a single charge for what was created.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The destination URL allows up to 2048 characters; the body, up to 64 KiB.\n- Destinations on `localhost`, `*.local`, `*.internal` or on private or reserved IPs are rejected; so are destinations that already are a short link (our domains or shorteners such as bit.ly, tinyurl.com or t.co); destinations with a user or password before the domain (`user@`); domains with accents, ñ or other alphabets (also as `xn--`), and URLs with invisible characters.\n- With a plan: up to 100,000 creations per Colombia day (daily quota).\n- Supports `Idempotency-Key`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "201 Created · Example",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  },
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Optional idempotency key (1 to 255 characters from `A-Za-z0-9_-`), unique per organization. If you repeat the creation with the same key and the same body, you get the link that was already created, without creating another one or charging again. See the [idempotency guide](https://developers.hablame.co/en/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": "Publish a short link with a generated code or your own alias. Charged on creation, unless your plan includes it.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nCreates a short link on one of the domains available to your organization. If you do not send `code`, the service generates a 7-character one (`0-9a-z`); if you do, it becomes an alias (`isAlias: true`). Codes are unique **per domain**, and the code of a deleted link cannot be used for another link: if it was yours, you can [restore it](https://developers.hablame.co/en/docs/reference/urlshortener/restore) for 90 days.\n\nEach created link is charged at your organization’s current price (see [`GET /api/v6/urlshortener/pricing`](https://developers.hablame.co/en/docs/reference/urlshortener/pricing)). If your organization has an active monthly URL shortener plan, creating links is not charged, and a **daily creation quota** applies instead (100,000 per Colombia day, unless you agreed another one): once reached, the API returns `429 URLSHORTENER_LINK_QUOTA_EXCEEDED` until midnight. Without a plan there is no daily quota: without available balance the API returns `402` and the link is not created.\n\n> **Retry without duplicates with `Idempotency-Key`.** If a creation is cut off (a timeout, a `5xx`) and you repeat it with the same key and the same body, you get the same link (`201` with `Idempotency-Status: replayed`) without creating another one or charging twice, even while the first one is still being processed. The same key with a different body returns `422 IDEMPOTENCY_KEY_REUSED`. On this endpoint the key stays tied to the link forever. Without a key, each retry creates and charges another link.\n\n> The body is validated **before** the balance is checked: an invalid URL, alias, domain or expiry returns its error without touching your balance.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\nMany links at once, for example for a campaign? Use [bulk creation](https://developers.hablame.co/en/docs/reference/urlshortener/batch): up to 1,000 per request, with the same rules and a single charge for what was created.\n\n## Scope and limits\n\n- What it does: Creates data in your organization.\n- Charge: The call is charged.\n- After that, every visit by a person to the link is also charged (visits by bots and link previews are not).\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.\n- The destination URL allows up to 2048 characters; the body, up to 64 KiB.\n- Destinations on `localhost`, `*.local`, `*.internal` or on private or reserved IPs are rejected; so are destinations that already are a short link (our domains or shorteners such as bit.ly, tinyurl.com or t.co); destinations with a user or password before the domain (`user@`); domains with accents, ñ or other alphabets (also as `xn--`), and URLs with invisible characters.\n- With a plan: up to 100,000 creations per Colombia day (daily quota).\n- Supports `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. Get the created link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"2. Get the created link\\\" was skipped because it belongs to a flow that changes data and charges. Set confirmoFlujos to si in the environment to run the flow.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "One of your links by domain and code.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nReturns one link of your organization with its lifetime counters. A deleted one, or one from another organization, returns `404`. One blocked by Hablame comes back with `status: blocked`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "One of your links by domain and code.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nReturns one link of your organization with its lifetime counters. A deleted one, or one from another organization, returns `404`. One blocked by Hablame comes back with `status: blocked`.\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 60 requests per minute.",
                  "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. Link statistics",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"3. Link statistics\\\" was skipped because it belongs to a flow that changes data and charges. Set confirmoFlujos to si in the environment to run the flow.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"The response is JSON\", function () {",
                  "  pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json');",
                  "});",
                  "pm.test(\"It has the API envelope\", 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(\"It has 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                },
                {
                  "key": "from",
                  "value": "2026-09-01",
                  "description": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "2026-09-23",
                  "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                  "disabled": true
                },
                {
                  "key": "granularity",
                  "value": "day",
                  "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                  "disabled": true
                },
                {
                  "key": "dimensions",
                  "value": "country,device,referrer",
                  "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                  "disabled": true
                },
                {
                  "key": "top",
                  "value": "10",
                  "description": "How many values each dimension (and each ranking) returns. Max 100.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "Range totals, a daily or hourly series and breakdowns by country, device, OS, browser or source.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nAnalytics for one link over a window of days: the range totals, a daily or hourly series and the values with the most visits for each dimension. Days and hours are Colombia’s (`America/Bogota`).\n\n- `totals.clicks` and `totals.uniqueClicks` are the range’s; `firstClickAt` and `lastClickAt`, the link’s lifetime.\n- The series is **sparse**: days or hours without visits are omitted. Fill them with zero if you are going to chart it.\n- Each dimension returns the `top` values with the most visits and, if there is a remainder, an `__other` row. A visit with no value for a dimension (no country, or no source) does not appear in it: its rows can add up to less than the total.\n- `referrer` is only the source domain (`www.facebook.com`), without the path. Direct visits have no row: they are `totals.clicks` minus the sum of `referrer`.\n- `device` is `mobile`, `tablet` or `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` or `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` or `Other`. `country` is the ISO code (`CO`).\n\n> `uniqueClicks` counts the first visit of the day from each browser. Over a multi-day range it is the **sum** of each day, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension.\n- On top of that, each query spends units from a quota of 120 per minute for your organization, according to its cost: 2 for the query and 3 for every 31 days of the range. 30 days spend 5; a year, 38. Without units, `429` with `Retry-After`.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "200 OK · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    },
                    {
                      "key": "from",
                      "value": "2026-09-01",
                      "description": "First day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to 29 days before `to` (a 30-day window).",
                      "disabled": true
                    },
                    {
                      "key": "to",
                      "value": "2026-09-23",
                      "description": "Last day of the window, `YYYY-MM-DD` in Colombia time, inclusive. Defaults to today.",
                      "disabled": true
                    },
                    {
                      "key": "granularity",
                      "value": "day",
                      "description": "Size of each point of the series. `day` allows windows of up to 366 days; `hour`, up to 31.",
                      "disabled": true
                    },
                    {
                      "key": "dimensions",
                      "value": "country,device,referrer",
                      "description": "Comma-separated breakdown dimensions: `country`, `subdivision`, `city`, `device`, `os`, `browser`, `referrer`. Defaults to `country,device,os,browser,referrer`. Unknown names are ignored.",
                      "disabled": true
                    },
                    {
                      "key": "top",
                      "value": "10",
                      "description": "How many values each dimension (and each ranking) returns. Max 100.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "Range totals, a daily or hourly series and breakdowns by country, device, OS, browser or source.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nAnalytics for one link over a window of days: the range totals, a daily or hourly series and the values with the most visits for each dimension. Days and hours are Colombia’s (`America/Bogota`).\n\n- `totals.clicks` and `totals.uniqueClicks` are the range’s; `firstClickAt` and `lastClickAt`, the link’s lifetime.\n- The series is **sparse**: days or hours without visits are omitted. Fill them with zero if you are going to chart it.\n- Each dimension returns the `top` values with the most visits and, if there is a remainder, an `__other` row. A visit with no value for a dimension (no country, or no source) does not appear in it: its rows can add up to less than the total.\n- `referrer` is only the source domain (`www.facebook.com`), without the path. Direct visits have no row: they are `totals.clicks` minus the sum of `referrer`.\n- `device` is `mobile`, `tablet` or `desktop`; `os`, `Android`, `iOS`, `Windows`, `macOS`, `Linux`, `ChromeOS` or `Other`; `browser`, `Chrome`, `Safari`, `Firefox`, `Edge`, `Opera`, `Samsung Internet`, `UC Browser` or `Other`. `country` is the ISO code (`CO`).\n\n> `uniqueClicks` counts the first visit of the day from each browser. Over a multi-day range it is the **sum** of each day, not distinct people. Responses can be up to one minute old (`X-Cache` header).\n\n## Scope and limits\n\n- What it does: Read only: it changes nothing.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 120 requests per minute.\n- Windows of up to 366 days by day, and up to 31 days by hour.\n- Up to 100 values per dimension.\n- On top of that, each query spends units from a quota of 120 per minute for your organization, according to its cost: 2 for the query and 3 for every 31 days of the range. 30 days spend 5; a year, 38. Without units, `429` with `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. Delete the link",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.environment.get(\"confirmoFlujos\") !== 'si') {",
                  "  console.log(\"Hablame: \\\"4. Delete the link\\\" was skipped because it belongs to a flow that changes data and charges. Set confirmoFlujos to si in the environment to run the flow.\");",
                  "  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(\"The status is the expected one\", function () {",
                  "  pm.expect(pm.response.code).to.be.oneOf([204]);",
                  "});",
                  "pm.test(\"It has the X-Request-Id header\", function () {",
                  "  pm.expect(pm.response.headers.has('X-Request-Id')).to.equal(true);",
                  "});",
                  "pm.test(\"It has no body\", 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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "domain",
                  "value": "{{flujoDominio}}",
                  "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                },
                {
                  "key": "code",
                  "value": "{{flujoCodigo}}",
                  "description": "Code or alias of the link. Compared in lowercase."
                }
              ]
            },
            "description": {
              "content": "The link stops redirecting and disappears from the API. Its code stays taken, and you can restore it for 90 days.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nA **soft** delete: the link stops redirecting right away (the visitor gets `404`) and disappears from every API response, statistics included. What was charged is kept, and who deleted it is recorded. Its code stays taken forever on that domain: nobody can create another link with it, but you can [restore this one](https://developers.hablame.co/en/docs/reference/urlshortener/restore), with no new charge, **for 90 days**. After that the link is archived with its visits and can no longer be restored; the code stays taken.\n\nA link blocked by Hablame (`status: blocked`) cannot be deleted: it returns `409 LINK_BLOCKED`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Deletes data in your organization.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
              "type": "text/markdown"
            }
          },
          "response": [
            {
              "name": "204 No Content · Example",
              "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": "With `released`, the link is the one on a domain that no longer belongs to your organization (the one the list returns with `domainStatus: released`), even if that name is today another domain with the same code. Without it, `domain` names the domain that has that name today. Any other value returns `400 VALIDATION_INVALID_PARAMETER`.",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "domain",
                      "value": "{{flujoDominio}}",
                      "description": "Domain of the link, as the API returns it in `domain`. Compared in lowercase. It names the link that answers at that short URL today; for one on a domain that no longer belongs to your organization (`domainStatus: released`), add `domainStatus=released`."
                    },
                    {
                      "key": "code",
                      "value": "{{flujoCodigo}}",
                      "description": "Code or alias of the link. Compared in lowercase."
                    }
                  ]
                },
                "description": {
                  "content": "The link stops redirecting and disappears from the API. Its code stays taken, and you can restore it for 90 days.\n\n**This request is part of a flow that changes data and charges.** To run it, set `confirmoFlujos` to `si` in the environment.\n\nA **soft** delete: the link stops redirecting right away (the visitor gets `404`) and disappears from every API response, statistics included. What was charged is kept, and who deleted it is recorded. Its code stays taken forever on that domain: nobody can create another link with it, but you can [restore this one](https://developers.hablame.co/en/docs/reference/urlshortener/restore), with no new charge, **for 90 days**. After that the link is archived with its visits and can no longer be restored; the code stays taken.\n\nA link blocked by Hablame (`status: blocked`) cannot be deleted: it returns `409 LINK_BLOCKED`.\n\n> **A key with the URL shortener also creates and deletes** The `urlshortener` service grants the whole URL shortener: with it, the API key lists and reads links, and also creates them (each creation is charged), edits, deletes and restores them. There are no read-only keys. Only the owner of your organization creates keys: if one must not create links, do not give it this service. Without it, the key gets `403 AUTH_SERVICE_NOT_ALLOWED`.\n\n## Scope and limits\n\n- What it does: Deletes data in your organization.\n- Charge: The call is not charged.\n- Access: API key with the `urlshortener` service enabled.\n- Limit: 35 requests per second.",
                  "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: the apiKey variable is empty. Paste your API key into the environment.\");",
          "}"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://developers.hablame.co",
      "type": "string",
      "description": "Base address of the API. Do not change it."
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Your API key. Paste it in the environment, never in the collection."
    },
    {
      "key": "flujoDominio",
      "value": "",
      "type": "string"
    },
    {
      "key": "flujoCodigo",
      "value": "",
      "type": "string"
    }
  ]
}