Unit 1.1: IONOS API, Authentication, and Async Model
Introduction
You are about to build TaskBoard, a task management API with a web frontend, and ship every piece of it through code on IONOS Cloud. No clicking around the Data Center Designer. Before you can provision a single server, you need three things wired correctly: how you authenticate, how the API tells you a resource is actually ready, and which client (raw HTTP, SDK, or CLI) you reach for in each situation.
This unit is the contract you sign with the IONOS Cloud API. The single most important fact to internalize is that provisioning is asynchronous: a POST returns immediately with a request ID, not a finished resource. Treat the response as "accepted, working on it" rather than "done," and you will avoid the most common class of automation bug on this platform. You will set up authentication, learn the polling loop that gates every dependent operation, and provision a server through all three interfaces so you can compare them directly.
1. The IONOS Cloud REST API
Every TaskBoard resource you create, from the datacenter to the server to the load balancer, goes through the IONOS Cloud REST API. The Cloud API is versioned and lives under a single base URL. All core CloudAPI calls target https://api.ionos.com/cloudapi/v6, and requests and responses are JSON (Content-Type: application/json).
Resources are addressed hierarchically. A server, for example, lives under its datacenter: /datacenters/{datacenterId}/servers/{serverId}. This nesting matters because you almost always create a parent before a child, and the parent has to be fully provisioned first (see Section 3).
1.1 Base URL, versioning, and content types
The CloudAPI surface is pinned to v6 in the path. Pin to it explicitly in your code rather than relying on an unversioned alias, so an upstream version bump never silently changes behavior under your automation.
# Smoke-test connectivity and auth: list your datacenters
curl -s -X GET 'https://api.ionos.com/cloudapi/v6/datacenters?depth=1' \
-H 'Authorization: Bearer '"$IONOS_TOKEN" \
-H 'Content-Type: application/json'
The depth query parameter controls how much of the nested resource tree is returned. depth=1 returns the collection with top-level properties; higher depths inline children. Keep depth low on list calls to reduce payload size, and request a specific resource by ID when you need full detail.
1.2 Note on separate API hosts
Not every IONOS service sits under cloudapi/v6. Some services expose their own hosts (for example, IAM Federation uses https://iam.ionos.com, and the CDN uses a regional host). When you integrate those services in later modules, read the endpoint from that service's documentation rather than assuming the core CloudAPI base. The authentication header, however, stays the same bearer token across these hosts.
2. Authentication
The IONOS Cloud API accepts two authentication methods: a bearer token (the primary, recommended method) and Basic auth using your account username and password.
Two operational facts drive the choice. First, accounts with 2FA enabled or enforced must use bearer token authentication. Second, Basic authentication is documented as being discontinued in the near future and should only be used in combination with 2FA. The practical takeaway for any new automation: use bearer tokens.
2.1 Bearer tokens via the Token Manager
You generate tokens through the API/SDK Authentication Token Manager (in the DCD under Menu > Management > Token Manager, via the API, or via the CLI). A token is a string you place in the Authorization header on every request.
# Bearer token on every CloudAPI request
curl -s -X GET 'https://api.ionos.com/cloudapi/v6/datacenters' \
-H 'Authorization: Bearer '"$IONOS_TOKEN"
You can request a token programmatically from the token generation endpoint, then reuse that token value for subsequent API and SDK calls:
# Generate a token using Basic auth, then switch to the token for all later calls
TOKEN=$(curl -s -u "$IONOS_USERNAME:$IONOS_PASSWORD" \
-X GET 'https://api.ionos.com/auth/v1/tokens/generate' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
export IONOS_TOKEN="$TOKEN"
Note the token generation host is auth/v1, not cloudapi/v6. The token you get back is then used against the CloudAPI.
2.2 Token lifecycle: creation, scoping, and rotation
The Token Manager imposes concrete limits you must design around. You can generate up to 100 authentication tokens per user, and each token's TTL is chosen at creation from a fixed set of options: 1 Hour, 4 Hours, 1 Day, 7 Days, 30 Days, 60 Days, 90 Days, 180 Days, and 365 Days.
The token value is shown exactly once at generation and is not recoverable afterward; you can also download it as a file at that moment. Capture it immediately into your secret store, because there is no "show again" later.
These limits shape a least-privilege, rotation-friendly pattern. Issue a separate, short-TTL token per service and per environment (one for the TaskBoard CI pipeline, one for the running API service, and so on) rather than sharing one long-lived token everywhere. Because tokens expire on a fixed TTL, rotation is a routine you automate rather than a fire drill.
# Read the token from the environment, never hardcode it
import os
IONOS_TOKEN = os.environ["IONOS_TOKEN"] # fails loudly if unset
# Hardcoding a 365-day token in source is the fastest way to leak credentials.
The 100-token ceiling means a runaway script that mints a fresh token per run will exhaust your quota. Mint once, store, reuse until expiry, then rotate.
3. The Async Provisioning Model
This is the rule that breaks more IONOS automation than any other: provisioning operations are asynchronous. A POST or PUT does not return a finished resource. It returns 202 Accepted, the new resource enters a BUSY state, and a Location header points to a status URL you poll until provisioning completes.
You must wait for completion before any operation that depends on the new resource. Attaching a volume to a server that is still BUSY, or creating a NIC on a half-provisioned datacenter, fails. The async model is not optional, and it is not something you can sidestep with retries on the dependent call alone.
3.1 The 202 response and the Location header
When you create a resource, the response status is 202 Accepted, the body contains the new resource id, and the response header includes a status URL to poll.
# Create a datacenter; capture the request status URL from the Location header
curl -s -D - -o /tmp/dc.json \
-X POST 'https://api.ionos.com/cloudapi/v6/datacenters' \
-H 'Authorization: Bearer '"$IONOS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"properties":{"name":"taskboard-dc","location":"de/fra"}}' \
| grep -i '^location:'
The Location header carries the request status URL. The pattern is identical across resource types: the documentation for the core Cloud API endpoints states that the response includes a Location header with a URL to poll the request status, and the resource holds a BUSY status until provisioning completes.
3.2 Polling until DONE
The status endpoint reports the request's state. You poll it until the state is DONE (a failed provisioning surfaces as FAILED). Only then do you proceed to dependent operations.
The exact request status payload shape and the recommended polling cadence below reflect common IONOS automation practice rather than a verbatim documentation excerpt. The state values BUSY, DONE, and FAILED are documentation-grounded; the loop structure is a standard implementation.
# Poll the status URL until the request reports DONE
STATUS_URL="https://api.ionos.com/cloudapi/v6/requests/<request-id>/status"
until [ "$(curl -s -H "Authorization: Bearer $IONOS_TOKEN" "$STATUS_URL" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["metadata"]["status"])')" = "DONE" ]; do
echo "still provisioning..."
sleep 5
done
echo "resource ready"
The SDKs wrap this loop for you. Pass the SDK option that waits for completion and the client polls internally, so your application code reads as if the call were synchronous while still respecting the async model underneath.
4. SDKs and the ionosctl CLI
You will not write raw curl for application logic. IONOS publishes SDKs for Python (ionoscloud), Go (sdk-go), Java, and JavaScript, plus the ionosctl command-line tool. All of them authenticate with the same bearer token and all of them have to honor the async model.
4.1 The Python SDK
The Python SDK uses a Configuration object (carrying your token) wrapped in an ApiClient, which you then hand to service-specific API classes.
The SDK class and method names below (Configuration, ApiClient, DataCentersApi, ServersApi, and the datacenters_servers_post method) reflect the ionoscloud Python SDK's published interface from general knowledge; verify exact signatures against the SDK version you pin.
import os
import ionoscloud
from ionoscloud.api import data_centers_api, servers_api
from ionoscloud.models import Server, ServerProperties
config = ionoscloud.Configuration(token=os.environ["IONOS_TOKEN"])
with ionoscloud.ApiClient(config) as api_client:
servers = servers_api.ServersApi(api_client)
server = Server(properties=ServerProperties(
name="taskboard-api",
cores=4,
ram=8192, # MB; 8 GB
cpu_family="INTEL_SKYLAKE",
))
# The SDK can wait for the async request to finish for you
created = servers.datacenters_servers_post(
datacenter_id=os.environ["TASKBOARD_DC_ID"],
server=server,
)
print("server id:", created.id)
The SDK reads your token from the Configuration object. Keep that object's lifetime tied to the token's TTL and refresh it when you rotate.
4.2 The ionosctl CLI
ionosctl is the fastest interface for one-off tasks, scripting, and inspection. Authenticate once, then run commands.
The ionosctl command syntax below reflects the tool's published command structure from general knowledge; confirm flags against your installed CLI version with ionosctl <command> --help.
# Authenticate the CLI with a token
ionosctl login --token "$IONOS_TOKEN"
# List datacenters
ionosctl datacenter list
# Create a server (ionosctl waits for the request by default)
ionosctl server create \
--datacenter-id "$TASKBOARD_DC_ID" \
--name taskboard-api --cores 4 --ram 8192
4.3 Choosing an interface
The following table compares the four ways you interact with the IONOS Cloud API so you can pick deliberately rather than by habit.
| Interface | Best for | Async handling | When to reach for it |
|---|---|---|---|
curl / raw HTTP |
Debugging, learning the wire format | You poll manually | Reproducing an issue, scripting in a language without an SDK |
| SDK (Python/Go/Java/JS) | Application code | Built-in wait option | Anything inside TaskBoard's services |
ionosctl |
One-off tasks, shell scripts | Waits by default | Quick inspection, glue scripts, CI steps |
| Terraform | Declarative infrastructure | Provider polls internally | Standing infrastructure (covered in Unit 1.2) |
As shown above, use the SDK for application logic, ionosctl for quick tasks and CI glue, raw HTTP when you are debugging the protocol itself, and Terraform (next unit) for everything that should live as declarative state.
5. Rate Limiting, Pagination, and Error Handling
Production automation hits three realities the happy-path examples skip: the API rate-limits you, collections are paginated, and some error codes mean something different than you expect.
5.1 Rate limiting with exponential backoff
When you exceed the request rate, the API responds with 429. The correct response is to back off exponentially and retry, not to hammer the endpoint.
The exponential-backoff implementation below is a standard client-side retry pattern; the 429 status semantics are documentation-grounded, the specific delays and jitter are an implementation choice.
import time, random, requests
def get_with_backoff(url, headers, max_retries=6):
for attempt in range(max_retries):
resp = requests.get(url, headers=headers)
if resp.status_code != 429:
resp.raise_for_status()
return resp
# Honor Retry-After if present, else exponential backoff with jitter
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait + random.uniform(0, 1))
raise RuntimeError("rate limit: retries exhausted")
5.2 Pagination with offset and limit
List endpoints return paginated collections controlled by offset and limit. The limit parameter caps the number of elements per page, and offset sets the starting point within the collection. On collection endpoints the default limit is 1000 and the default offset is 0.
# Iterate every page of a collection
def list_all(url, headers, page_size=1000):
offset, items = 0, []
while True:
page = get_with_backoff(
f"{url}?offset={offset}&limit={page_size}", headers
).json()
batch = page.get("items", [])
items.extend(batch)
if len(batch) < page_size:
break
offset += page_size
return items
Never assume a single call returned everything. If you got exactly limit items back, there is almost certainly another page.
5.3 Error handling and the 404 trap
The error you will misread first is 404 during provisioning. A 404 immediately after creating a resource usually means the resource is not ready yet, not that it is missing. This is the async model biting you: you skipped the poll and queried a child resource before its parent reached DONE.
# WRONG: create then immediately use -> intermittent 404
created = servers.datacenters_servers_post(datacenter_id=dc_id, server=server)
volumes.datacenters_volumes_post(datacenter_id=dc_id, volume=vol) # may 404
# RIGHT: wait for DONE, then proceed (SDK wait option, or poll the status URL)
Read response bodies on failure. IONOS error responses include a structured payload with the HTTP code and a human-readable message; log it rather than swallowing the exception, so a 429, a malformed body (422), and an auth failure (401) are immediately distinguishable in your pipeline output.
API Reference Quick Card
Key API endpoints for authentication and the async model:
| Method | Endpoint | Description |
|---|---|---|
GET |
/auth/v1/tokens/generate |
Generate a bearer token |
GET |
/cloudapi/v6/datacenters |
List datacenters (smoke-test auth) |
POST |
/cloudapi/v6/datacenters/{dcId}/servers |
Create a server (returns 202) |
GET |
/cloudapi/v6/requests/{requestId}/status |
Poll async request status until DONE |
GET |
/cloudapi/v6/datacenters/{dcId}/servers/{serverId} |
Get server details |
Base URL: https://api.ionos.com/cloudapi/v6
Token host: https://api.ionos.com/auth/v1
Authentication: Authorization: Bearer <token>
Code Lab
Objective: Provision one server three ways (curl, Python SDK, ionosctl) and verify async completion each time.
Prerequisites:
- IONOS Cloud account with API access
curl,python3, andionosctlinstalled locally- The
ionoscloudPython SDK:pip install ionoscloud
Step 1: Generate and export a token
export IONOS_TOKEN=$(curl -s -u "$IONOS_USERNAME:$IONOS_PASSWORD" \
-X GET 'https://api.ionos.com/auth/v1/tokens/generate' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
Expected output:
(no output; verify with: echo ${IONOS_TOKEN:0:8}...)
Step 2: Create a datacenter and capture the request status URL
curl -s -D /tmp/hdr -o /tmp/dc.json \
-X POST 'https://api.ionos.com/cloudapi/v6/datacenters' \
-H "Authorization: Bearer $IONOS_TOKEN" -H 'Content-Type: application/json' \
-d '{"properties":{"name":"taskboard-dc","location":"de/fra"}}'
grep -i '^location:' /tmp/hdr
export DC_ID=$(python3 -c 'import json;print(json.load(open("/tmp/dc.json"))["id"])')
Expected output:
location: https://api.ionos.com/cloudapi/v6/requests/<id>/status
Step 3: Poll until the datacenter is DONE
STATUS=$(grep -i '^location:' /tmp/hdr | awk '{print $2}' | tr -d '\r')
until [ "$(curl -s -H "Authorization: Bearer $IONOS_TOKEN" "$STATUS" \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["metadata"]["status"])')" = "DONE" ]; do sleep 5; done
echo done
Expected output:
done
Step 4: Create a server via curl
curl -s -X POST "https://api.ionos.com/cloudapi/v6/datacenters/$DC_ID/servers" \
-H "Authorization: Bearer $IONOS_TOKEN" -H 'Content-Type: application/json' \
-d '{"properties":{"name":"srv-curl","cores":2,"ram":4096,"cpuFamily":"INTEL_SKYLAKE"}}'
Expected output:
{"id":"<server-id>","type":"server", ... }
Step 5: Create a server via the Python SDK
import os, ionoscloud
from ionoscloud.api import servers_api
from ionoscloud.models import Server, ServerProperties
cfg = ionoscloud.Configuration(token=os.environ["IONOS_TOKEN"])
with ionoscloud.ApiClient(cfg) as c:
s = servers_api.ServersApi(c).datacenters_servers_post(
datacenter_id=os.environ["DC_ID"],
server=Server(properties=ServerProperties(
name="srv-sdk", cores=2, ram=4096, cpu_family="INTEL_SKYLAKE")))
print("sdk server:", s.id)
Expected output:
sdk server: <server-id>
Step 6: Create a server via ionosctl
ionosctl login --token "$IONOS_TOKEN"
ionosctl server create --datacenter-id "$DC_ID" --name srv-cli --cores 2 --ram 4096
Expected output:
ServerId Name Cores Ram State
<id> srv-cli 2 4096 BUSY -> AVAILABLE
Step 7: List all servers and confirm three exist
ionosctl server list --datacenter-id "$DC_ID"
Expected output:
srv-curl, srv-sdk, srv-cli all AVAILABLE
Validation Checklist:
- [ ] Token generated and exported, never hardcoded
- [ ] Datacenter reached
DONEbefore any server was created - [ ] Three servers created via three different interfaces
- [ ] All three servers reach
AVAILABLEstate
Cleanup:
# Deleting the datacenter removes its child servers; avoids ongoing charges
ionosctl datacenter delete --datacenter-id "$DC_ID" --force
Common Pitfalls
-
Treating provisioning as synchronous
- Problem: Your script creates a server and immediately attaches a volume, getting an intermittent
404or a conflict error. - Why it happens: The
POSTreturned202 Acceptedwith the resource inBUSYstate; the resource is not ready yet. - Fix: Poll the status URL from the
Locationheader untilDONE, or use the SDK's wait-for-completion option, before any dependent call.
- Problem: Your script creates a server and immediately attaches a volume, getting an intermittent
-
Minting a fresh token on every run
- Problem: A scheduled job stops working after a while with auth errors, and you find dozens of tokens in the Token Manager.
- Why it happens: Each user is capped at 100 tokens; a script that generates one per run exhausts the quota and loses track of which token is live.
- Fix: Generate one scoped token per service/environment, store it in a secret store, reuse until its TTL expires, then rotate. The token value is shown only once, so capture it at creation.
-
Reading only the first page of a collection
- Problem: A list operation silently misses resources beyond the first 1000 items.
- Why it happens: Collection endpoints paginate with a default
limitof1000; code that ignoresoffset/limitsees one page. - Fix: Loop with increasing
offsetuntil a page returns fewer thanlimititems (see Section 5.2).
Summary
You now hold the contract every later unit depends on. You can authenticate with a bearer token from the Token Manager, respect the async provisioning model by polling requests/{id}/status until DONE, and provision the same resource through curl, the Python SDK, and ionosctl. You can also keep automation alive under load by backing off on 429 and paging through large collections. With this foundation, TaskBoard's infrastructure work in Unit 1.2 becomes a matter of expressing these same operations declaratively in Terraform.
Key Points:
- The CloudAPI base URL is
https://api.ionos.com/cloudapi/v6; tokens are generated athttps://api.ionos.com/auth/v1/tokens/generate. - Provisioning is asynchronous:
POSTreturns202 Accepted, the resource goesBUSY, and you poll theLocationstatus URL untilDONEbefore dependent operations. - Use bearer tokens (Basic auth is being discontinued and 2FA accounts must use bearer); a user can hold up to 100 tokens, each with a fixed TTL from 1 Hour to 365 Days.
- A token value is shown exactly once and is not recoverable, so capture it at creation; you can download it as a file at that moment.
- A
404right after creation usually means "not ready yet," not "missing"; handle429with exponential backoff and page collections withoffset/limit(default limit1000).
Important Terminology:
- Bearer token: A string credential issued by the Token Manager, sent in the
Authorization: Bearerheader, and the primary auth method for the IONOS Cloud API. - Async provisioning model: The platform behavior where create/update calls return
202and aLocationstatus URL; the resource isBUSYuntil provisioning reachesDONE. - Request status endpoint:
/cloudapi/v6/requests/{id}/status, polled to learn whether an async operation isBUSY,DONE, orFAILED. - Token TTL: The fixed lifetime chosen at token creation (1 Hour through 365 Days) that drives your rotation schedule.
- Pagination (offset/limit): Collection-endpoint parameters where
limitcaps items per page (default1000) andoffsetsets the starting index.
Next Steps
Continue Learning: Unit 1.2: Terraform Provider and Core Patterns
Related Topics: