Skip to content

Add POST /v1/links/bulk for atomic bulk link creation#112

Merged
saltyskip merged 2 commits intomainfrom
bulk-links-create
May 1, 2026
Merged

Add POST /v1/links/bulk for atomic bulk link creation#112
saltyskip merged 2 commits intomainfrom
bulk-links-create

Conversation

@saltyskip
Copy link
Copy Markdown
Owner

Summary

  • New POST /v1/links/bulk endpoint creates up to 100 links from a shared template in one atomic call. Two modes: caller-supplied custom_ids or auto-generated via count. Gated on a verified custom domain (intended use case: campaign codes / affiliate slugs under a customer's own domain).
  • All-or-nothing via Mongo transaction. A race that takes one of our chosen ids rolls back the whole batch and surfaces every conflict in one response so the caller can fix and retry in one pass.
  • Quota pre-checked once via new QuotaChecker::check_n — batch is one decision, not N. Counts as one unit against the rate limiter.
  • Exposed as the create_links MCP tool too. Service-layer choke point means both transports go through the same validation, quota, and transaction path (per CLAUDE.md "quota lives in services").

Response shape

Success (201):

{ "links": [{"link_id": "partner-acme", "url": "https://go.acme.com/partner-acme"}, ...] }

Per-row failures (400 invalid_batch) carry every problem at once:

{
  "error": "2 item(s) failed validation",
  "code": "invalid_batch",
  "errors": [
    {"index": 1, "custom_id": "bad id!", "code": "invalid_custom_id", "message": "..."},
    {"index": 4, "custom_id": "promo-2", "code": "link_id_taken", "message": "..."}
  ]
}

Test plan

  • 11 new service unit tests cover empty / too-large / mode ambiguity / no-domain / template URL / template threat / per-row format / intra-batch dupe / DB-side dupe / quota / both happy paths
  • cargo fmt -- --check, cargo clippy --all-targets -- -D warnings, cargo test (97 lib + 145 integration) all green
  • Manual sanity check needed against Atlas dev cluster before merge — the transaction path (insert_many_with_session) isn't exercised by tests since the mock repo skips the session. The duplicate-detection + rollback contract is covered, but the actual transaction round trip is not.

🤖 Generated with Claude Code

Lets customers mint up to 100 links from a shared template in one call,
either with caller-supplied vanity slugs or auto-generated IDs. Gated on
a verified custom domain (the intended use case is campaign codes /
affiliate slugs under a customer's own domain) so bulk-created links
also skip the 30-day expiry that applies to free-tier links.

All-or-nothing via a Mongo transaction: a race that takes one of our
chosen ids rolls back the whole batch and surfaces every conflict in
one response so the caller can fix and retry in one pass. Quota is
pre-checked once via a new QuotaChecker::check_n so a batch is one
decision rather than failing partway through.

Exposed as the create_links MCP tool too — service-layer choke point
keeps both transports going through the same validation, quota, and
transaction path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rift Ready Ready Preview, Comment May 1, 2026 1:16pm

Request Review

Every CreateLinkInput field is already pub Option<T>, so the if-let
chain was unwrapping just to re-wrap through builder methods that take
T. The struct literal is the same code without the round trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@saltyskip saltyskip merged commit 001c69c into main May 1, 2026
5 checks passed
@saltyskip saltyskip deleted the bulk-links-create branch May 1, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant