Create links in bulk
POST /v1/links/bulk accepts a JSON object with a links array of 1–1,000
link creation inputs. The entire request body must fit within 1 MiB. Use a key
with links:write whose creator can create resources in the workspace.
Each row supports the same destination, domain, key, title, description, tags and
password options, validation and plan gates as POST /v1/links.
curl https://api.shortn.at/v1/links/bulk \
-H "Authorization: Bearer $SHORTN_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: campaign-launch-001' \
--data '{"links":[{"url":"https://example.com/first","tags":["Launch"]},{"url":"https://example.com/second","tags":["Launch"]}]}'
Results and atomicity
A successful request returns HTTP 201 with count and items in the input order.
Each item has the same shape as a newly created individual link. Destination
safety starts pending, with a durable background scan request committed alongside
each link. The worker scans new destinations and retries failures; configured
safety and password gates still apply at redirect time. If there are enabled
subscribers, each link produces its own link.created event.
The batch commits in one Mongo transaction. Invalid input, taken or duplicate keys, a missing plan feature, insufficient quota, or a database/event write failure creates no batch links or new tags. A workspace quota check covers the entire batch and is serialized with other v2 link and QR backing-link creation requests. Keys are checked again inside the transaction; database uniqueness handles racing inserts. Foreign-workspace tags are never reused, including when legacy owner fields happen to match.
Errors use application/problem+json. Validation or key errors may include a
context.index identifying the zero-based input row. HTTP 409 limit_reached
includes context.limit, context.used and context.requested so the integration
can show the exact remaining capacity. A validation failure in the request envelope
returns HTTP 400; an oversized body returns HTTP 413.
Retries
Send a unique Idempotency-Key for each intended batch and retain it for retries.
The original status and response are retained for 24 hours. Reusing the key with a
different request returns HTTP 409 idempotency_conflict. An uncertain in-flight
operation returns idempotency_pending; it is not reissued automatically.
Feature flags, membership, role and scopes are checked before replaying a response.
This endpoint creates a bounded batch. CSV upload, mapping, dry runs, whole-import quota reservation and resumable background imports remain separate work.