Urlicer API v1

Bulk create

Create up to 10,000 links in one request, and read the per-row results.

If you are importing a spreadsheet or generating a link per customer, send the links in one request instead of one request each. The whole batch costs a single request against your per-minute budget, so an import that would take over an hour one link at a time takes seconds.

Create links in bulk

POST /api/v1/links/bulk

Up to 10,000 links in a single request, with a result row for each one.

Body fields
Field Type Required Description
links array required The links to create, at most 10,000. Each entry takes url, and optionally alias, expiresOn, password, title, notes, maxClicks, startsOn and fallbackUrl: the same fields as a single create.
domain string optional A custom domain of yours, applied to every link in the batch. "default" uses the workspace's default domain; without the field the batch stays on ivi.to.
expiresOn integer optional A default expiry in unix seconds for the whole batch. An expiry on an individual link wins over it.
maxClicks integer optional, Pro and above A default click limit for the whole batch. A value on an individual link wins over it.
startsOn integer optional, Pro and above A default start in unix seconds for the whole batch. A value on an individual link wins over it.
fallbackUrl string optional, Pro and above A default fallback URL for the whole batch. A value on an individual link wins over it. A row whose limits are not valid fails on its own, with the reason.
folder string optional A folder for the whole batch, by path: "Campaigns/Q4". Levels that do not exist are created, once, before the batch runs. There is no per-row folder: send one batch per folder, or move the rest afterwards with PATCH.
Request
curl -X POST "https://urlicer.com/api/v1/links/bulk" \
  -H "Authorization: Bearer url_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "links": [
    {"url": "https://example.com/one"},
    {"url": "https://example.com/two", "alias": "product-launch", "title": "Product launch"},
    {"url": "nope"}
  ],
  "folder": "Campaigns/Q4",
  "expiresOn": 1790000000
}'
Response
{
  "created": 2,
  "failed": 1,
  "results": [
    {
      "url": "nope",
      "short": null,
      "code": null,
      "success": false,
      "error": "Wrong URL format.",
      "warning": null
    },
    {
      "url": "https://example.com/one",
      "short": "https://ivi.to/Aipw",
      "code": "Aipw",
      "success": true,
      "error": null,
      "warning": null
    },
    {
      "url": "https://example.com/two",
      "short": "https://ivi.to/product-launch",
      "code": "product-launch",
      "success": true,
      "error": null,
      "warning": null
    }
  ]
}
Response fields
Field Type Description
created integer How many links were created.
failed integer How many rows did not produce a link.
results array One row per link you sent, described below. Not in the order you sent them.
results[].url string The destination from your request, which is how you match a row back to your input. On a created row it is the destination as saved: if your workspace's UTM naming rules lowercase tags, its utm_source, utm_medium and utm_campaign may differ in case from what you sent.
results[].short string or null The finished short link, or null if this row failed.
results[].code string or null The code alone, or null if this row failed.
results[].success boolean Whether this row produced a link.
results[].error string or null Why this row failed, in plain words. Null on success.
results[].warning string or null Set on a created row that broke your workspace's UTM naming rules while they warn, saying what. Null otherwise.
  • Bulk creation is a paid feature. On a plan without it the whole request is refused with a feature error and nothing is created.
  • The entire batch counts as one request against your per-minute rate limit, however many links it carries.
  • A batch folder is created before any link is, so a folder path that cannot be made, such as one deeper than five levels, refuses the whole request and creates nothing. The result rows do not repeat the folder: every created link is in it.
  • Your workspace's UTM naming rules are applied to each link on its own. When they block, a link with a value that is not on a list fails with the reason in "error" and the rest of the batch is still created.

Some rows can fail while others succeed

This is the one endpoint that does not answer all-or-nothing. A batch with a malformed URL in the middle of it still creates every other link, and tells you which one it dropped and why. That is deliberate: a thousand-row import should not be thrown away because row 400 has a typo in it.

So a 200 here does not mean everything worked. Read failed, and if it is not zero, read the rows.

The result rows are not in the order you sent them. Rows that failed validation come first. Next come the links that were created, and after them the rows refused at creation, such as a destination that is not allowed or is already a short link, or an alias that is taken. Match rows to your input by the url field, never by position. If the same destination appears twice in your batch, expect two rows carrying it.

What still fails the whole batch

These are checked before any link is created, and each of them refuses the entire request:

  • A feature your plan does not include. If any row asks for a custom alias, an expiry or a password, or the batch names a custom domain, and your plan does not include that, the request is refused with a feature error naming it. One row asking for a gated feature stops the batch, so filter those rows out before sending.
  • A domain that is not yours, or not verified and active. The request answers 400 validation.
  • More than 10,000 links. Split the work and send it as several batches.
  • Not enough monthly link allowance for the whole batch. You get a quota error and nothing is created. The batch is never half-applied to fit the remaining allowance.

Rows that fail validation, such as a URL that is not a URL or an alias longer than 40 characters, are handled differently. They never reach creation, so they cost no allowance and do not stop the batch. They come back as failed rows.

Aliases in a batch

A row whose alias is already taken fails on its own, with the rest of the batch unaffected. Its error says so in words rather than as a code: the machine-readable alias_taken error belongs to the single-link create, which has just one alias to talk about.

If you are generating aliases yourself, generate them idempotently, from a record id say, so that re-running a failed import produces the same aliases rather than a second set of links pointing at the same places.