Bulk operations
The Pivotal API doesn’t expose bulk endpoints (e.g. there is no POST /customers/bulk that takes an array). The reason: bulk endpoints either lie about partial-failure handling, or push the complexity into a job queue you can’t see into.
For now, fan out from your side. The patterns below handle 95% of bulk imports — backfills, migrations, periodic refreshes.
Sizing the work
Two numbers matter:
- Rate limit: 60 requests/min on a default key, 600/min on paid. Each bulk operation is
Nrequests, whereNis your record count. - Worker concurrency: how many requests you have in flight at once.
A safe default for a 60/min key: concurrency of 4, with each task waiting briefly between requests. That keeps you well under the bucket while staying fast enough to finish 5,000 records in ~85 minutes.
For 600/min: concurrency of 10 finishes 5,000 records in under 10 minutes.
A bulk import script
Three things to call out:
Idempotency-Keylets you re-run after a crash without duplicating records.- The checkpoint file is a flat-text list of
external_ids. Crude but enough for a one-shot script. For long-running services, use Redis or a Postgres row. slug_takenis accepted as a non-error — it means the customer was created in a previous run.
Why per-record Idempotency-Key
If you send the same body to POST /customers twice without a key, you get 409 slug_taken on the second attempt. With a key, you get 200 OK with the original response — which lets the script keep moving and treat it as “already done”.
Throttling against X-RateLimit-Remaining
The script above retries on 429. You can do better — slow down before you hit zero:
This drops your peak throughput slightly but eliminates 429 round trips entirely.
What to do about contacts and onboardings
Bulk-importing customers is the main case. If you also need to attach contacts and onboardings, fan them out the same way after the customer pass finishes:
Three passes is more wall time but easier to reason about than interleaving. The total request count is the same.
Reporting
After the script finishes, eyeball:
- Checkpoint count vs source count — should match.
- Errors — anything that isn’t
slug_taken, look at the error and decide. - The Pivotal API Keys page — confirms the request count tracks what your script logged.
What NOT to do
- Don’t disable retries. Network blips are real. The script above retries automatically on 429; add retries on transient 5xx too.
- Don’t share keys across bulk jobs. One key per bulk run makes the API Keys page a useful audit trail.
- Don’t fan out without a concurrency limit.
Promise.allover 5,000 records means 5,000 in-flight requests. You’ll trip the rate limit and the OS file descriptor limit before you trip anything else. - Don’t import then mutate the same row in the same pass. Two requests against the same record concurrently is a race. Let pass 1 finish before mutating.