16 min read

Learning Objectives

By the end of this module, you will be able to:

  • Authenticate against the IONOS Cloud API using bearer tokens from the Token Manager and Basic auth, and manage token lifecycle (creation, scoping, rotation) programmatically
  • Implement the asynchronous provisioning model correctly by polling the request status endpoint until `DONE` before running dependent operations
  • Provision the same resource three ways (raw `curl`, the Python `ionoscloud` SDK, and `ionosctl`) and choose the right interface for a given task
  • Handle rate limiting (`429`) with exponential backoff and iterate large collections with offset/limit pagination
  • Debug common API failures, including the `404`-during-provisioning trap that costs developers hours

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, and ionosctl installed locally
  • The ionoscloud Python 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 DONE before any server was created
  • [ ] Three servers created via three different interfaces
  • [ ] All three servers reach AVAILABLE state

Cleanup:

# Deleting the datacenter removes its child servers; avoids ongoing charges
ionosctl datacenter delete --datacenter-id "$DC_ID" --force

Common Pitfalls

  1. Treating provisioning as synchronous

    • Problem: Your script creates a server and immediately attaches a volume, getting an intermittent 404 or a conflict error.
    • Why it happens: The POST returned 202 Accepted with the resource in BUSY state; the resource is not ready yet.
    • Fix: Poll the status URL from the Location header until DONE, or use the SDK's wait-for-completion option, before any dependent call.
  2. 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.
  3. 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 limit of 1000; code that ignores offset/limit sees one page.
    • Fix: Loop with increasing offset until a page returns fewer than limit items (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 at https://api.ionos.com/auth/v1/tokens/generate.
  • Provisioning is asynchronous: POST returns 202 Accepted, the resource goes BUSY, and you poll the Location status URL until DONE before 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 404 right after creation usually means "not ready yet," not "missing"; handle 429 with exponential backoff and page collections with offset/limit (default limit 1000).

Important Terminology:

  • Bearer token: A string credential issued by the Token Manager, sent in the Authorization: Bearer header, and the primary auth method for the IONOS Cloud API.
  • Async provisioning model: The platform behavior where create/update calls return 202 and a Location status URL; the resource is BUSY until provisioning reaches DONE.
  • Request status endpoint: /cloudapi/v6/requests/{id}/status, polled to learn whether an async operation is BUSY, DONE, or FAILED.
  • 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 limit caps items per page (default 1000) and offset sets the starting index.

Next Steps

Continue Learning: Unit 1.2: Terraform Provider and Core Patterns

Related Topics: