Most Anaplan integrations start with a scheduled job someone wrote once and nobody wants to touch. It works until a token expires, a 50 MB file times out, or an import fails silently and finance spends a day reconciling numbers that were never loaded.
CloudWorks, Data Orchestrator, and Anaplan Connect all cover common ground, but sooner or later you need the layer underneath them: the Anaplan REST API v2. This tutorial walks through it the way a developer actually meets it — authenticate, find the IDs, push a file in chunks, run a process, wait for the result, and read the failure dump when it goes wrong.
All calls below use the integration base URL https://api.anaplan.com/2/0 and the authentication service at https://auth.anaplan.com. Replace IDs with your own.
1. Pick an authentication method before you write any code
There are three practical options, and the choice has real operational consequences.
| Method | Best for | Rotation burden | Notes |
|---|---|---|---|
| Basic auth | Quick experiments only | Password policy applies | Avoid for anything scheduled; disabled in many tenants |
| Certificate (CA-signed) | Long-running server jobs | Annual cert renewal | Works with Anaplan Connect and custom clients |
| OAuth 2.0 client | New integrations, rotation-friendly | Refresh token upkeep | Preferred for anything you plan to keep |
For new work, use OAuth 2.0. A tenant administrator registers an OAuth client in Anaplan Administration and gives you a client_id. Two grant types matter:
- Device grant — for headless services where a human authorises once at setup. You receive a refresh token that your job then uses indefinitely (subject to your tenant's rotation policy).
- Authorization code grant — for interactive apps where each user authenticates as themselves.
Whichever you use, every API call ends up carrying the same header:
Authorization: AnaplanAuthToken <token>
for Anaplan-issued tokens, or Authorization: Bearer <access_token> for OAuth access tokens. Tokens are short-lived — around 35 minutes for an Anaplan auth token. A long-running load must refresh mid-flight.
Device grant, end to end
Step one: request a device code.
curl -s -X POST https://us1a.app.anaplan.com/oauth/device/code \
-H 'Content-Type: application/json' \
-d '{"client_id":"YOUR_CLIENT_ID","scope":"openid profile email offline_access"}'
The response contains a device_code, a user_code, and a verification_uri. A human opens that URI once, signs in, and enters the code.
Step two: exchange the device code for tokens.
curl -s -X POST https://us1a.app.anaplan.com/oauth/token \
-H 'Content-Type: application/json' \
-d '{
"grant_type":"urn:ietf:params:oauth:grant-type:device_code",
"client_id":"YOUR_CLIENT_ID",
"device_code":"THE_DEVICE_CODE"
}'
Store the refresh_token in a secret manager — never in the repository, never in a shell script on the integration server. From then on, your job exchanges the refresh token for an access token at the start of each run:
curl -s -X POST https://us1a.app.anaplan.com/oauth/token \
-H 'Content-Type: application/json' \
-d '{"grant_type":"refresh_token","client_id":"YOUR_CLIENT_ID","refresh_token":"..."}'
If your tenant rotates refresh tokens on use, your job must write the new refresh token back to the secret store on every run. Skipping this is the single most common cause of an integration that works for a month and then dies quietly.
2. Find your IDs
Everything in the API is addressed by ID, not by name. Discover them once and cache them in configuration; do not look them up by name on every run, because someone will rename a model.
# Workspaces you can see
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/workspaces?tenantDetails=true"
# Models in a workspace
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/workspaces/$WS/models"
# Import definitions, exports, actions, processes
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/models/$MODEL/imports"
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/models/$MODEL/exports"
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/models/$MODEL/actions"
curl -s -H "$AUTH" "https://api.anaplan.com/2/0/models/$MODEL/processes"
The workspaces?tenantDetails=true call also returns workspace size and allowance, which is handy for a capacity dashboard.
3. Upload a file in chunks
A file in Anaplan is a named object attached to an import definition. You do not create the file through the API — a model builder uploads a template version once so the import definition knows its columns. After that, the API replaces the contents.
The rule of thumb: chunk anything over about 10 MB, and chunk everything in production anyway. Chunks should be 1–50 MB; 10 MB is a good default.
# 1. Split locally
split -b 10m actuals.csv chunk_
# 2. Declare the chunk count
curl -s -X PUT -H "$AUTH" -H 'Content-Type: application/json' \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/files/$FILE_ID" \
-d '{"id":"'$FILE_ID'","chunkCount":3}'
# 3. Upload each chunk, zero-indexed, in order
curl -s -X PUT -H "$AUTH" -H 'Content-Type: application/octet-stream' \
--data-binary @chunk_aa \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/files/$FILE_ID/chunks/0"
# 4. Mark the upload complete
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/files/$FILE_ID/complete" \
-d '{"id":"'$FILE_ID'","chunkCount":3}'
Practical points that bite people:
- Split on line boundaries.
split -bdoes not care about newlines. Usesplit -lwith a line count, or chunk the file in your own code, or the row straddling the boundary will be mangled. - Include the header only in chunk 0. The import definition expects one header row for the whole file.
- Retry a failed chunk, not the whole file. Chunks are individually addressable; that is the entire point.
- Set
chunkCountto -1 if you want to stream chunks without knowing the total up front, then call/completewhen done.
4. Run the import — or better, a process
Call a single import if you must, but in practice wrap the load in a process in the model: clear the staging module, import, run the calculation actions, then export the audit view. One API call, one task to monitor, and the sequence lives in Anaplan where a model builder can change it without a code release.
TASK=$(curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/processes/$PROCESS_ID/tasks" \
-d '{"localeName":"en_US"}' | grep -o '"taskId":"[^"]*' | cut -d'"' -f4)
The POST returns immediately with a taskId. Nothing is finished yet.
5. Poll the task, and interpret the result properly
while true; do
RESP=$(curl -s -H "$AUTH" \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/processes/$PROCESS_ID/tasks/$TASK")
echo "$RESP"
case "$RESP" in
*'"taskState":"COMPLETE"'*) break ;;
esac
sleep 10
done
taskState moves through NOT_STARTED, IN_PROGRESS, COMPLETE. Note carefully: COMPLETE does not mean success. Inside the response, result.successful is the boolean that matters, and result.failureDumpAvailable tells you whether rows were rejected.
A correct integration checks three things:
taskState == "COMPLETE"— the task finished.result.successful == true— no hard failure.result.failureDumpAvailable == false— no rejected rows.
Treating condition 1 as success is why teams discover in March that 4% of cost centre rows have been silently dropped since January.
6. Always fetch the failure dump
If a dump is available, download it and keep it. It is a CSV of rejected rows with reasons, and it is the difference between a five-minute fix and an afternoon of guessing.
curl -s -H "$AUTH" \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/processes/$PROCESS_ID/tasks/$TASK/dump" \
-o dump.csv
For multi-step processes the dump is per nested result, so iterate result.nestedResults[] and pull each dump that is flagged. Post the row count and the top three failure reasons into the same channel that gets your job alerts. Most rejects are unmapped list members — a genuine data-governance signal, not a technical error.
7. Exporting data out
Exports follow the same shape in reverse: run the export action, poll the task, then download the file in chunks.
curl -s -X POST -H "$AUTH" -H 'Content-Type: application/json' \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/exports/$EXPORT_ID/tasks" \
-d '{"localeName":"en_US"}'
# then, per chunk
curl -s -H "$AUTH" -H 'Accept: application/octet-stream' \
"https://api.anaplan.com/2/0/workspaces/$WS/models/$MODEL/files/$EXPORT_ID/chunks/0" \
-o out_0.csv
For small, targeted reads — a single module view, filtered — the Transactional API (/models/{id}/views/{viewId}/data) is often a better fit than a full export: no task, no chunking, JSON or CSV straight back. Keep it to modest result sets; it is not a bulk channel.
8. Production hardening checklist
What separates a script from an integration:
- Idempotency. If the job reruns, the outcome must be the same. Clear the staging module inside the process rather than relying on the import to overwrite.
- Concurrency guard. Two imports into the same module at once produce results nobody can explain. Take a lock in your scheduler.
- Token refresh mid-run. Long loads outlive tokens. Refresh on a timer, not only at startup.
- Backoff on 429 and 5xx. Anaplan rate-limits. Exponential backoff with jitter, capped retries.
- Structured logging. Log task IDs. When someone asks what happened at 02:14 last Thursday, the task ID is the only thread that leads anywhere.
- Alert on reject counts, not just failures. A load that succeeds with 900 rejects is a failure with better manners.
- Never point a new job at production first. Run it against a TEST model that is in deployed mode with production-like data.
When to use the API — and when not to
Use the REST API directly when you need orchestration your platform already owns (Airflow, dbt, a data platform job), unusual file handling, or logic that has to sit between systems. Use CloudWorks for straightforward cloud-storage-to-Anaplan schedules, Anaplan Data Orchestrator for governed, transformation-heavy pipelines, and Anaplan Connect when you want a supported CLI wrapper rather than your own code.
The API is the floor beneath all of them. Knowing it means you are never blocked by a tool's limitations — and you can debug the tools when they misbehave.
Need this built properly? QuanticPlanning's Anaplan developers build and harden integrations, from a single scheduled load to a governed enterprise data pipeline. Get in touch to scope an engagement, or read more about our Anaplan Data Integration Services.