AtlasWork, planned itself.

The AI-native, all-in-one work platform. Tasks, projects, CRM, contracts, and analytics in one calm workspace.

All systems operational
  • SOC 2 II
  • ISO 27001
  • HIPAA
  • GDPR

Product

  • Overview
  • PDF tools
  • Diagram tools
  • People & HR
  • Integrations
  • Marketplace
  • Pricing

Resources

  • Guides
  • Glossary
  • Compare
  • Docs
  • API reference
  • Support
  • Changelog
  • Status

Company

  • About
  • Careers
  • Press
  • Contact

Legal & trust

  • Trust center
  • Security
  • Privacy
  • Terms
  • DPA
  • GDPR
  • SLA
  • Refunds
  • Google API data
Atlas, a product by wrxstack.com·© 2026 wrxstack·All rights reserved
PrivacyTermsSecurityStatus
Skip to documentation
Docs
Back to Atlas

Start here

  • Overview

Developer

  • REST API guide
  • Authentication
  • API reference
  • MCP (AI agents)
  • SDKs
  • Quick actions

Webhooks

  • Overview
  • Quickstart
  • Events
  • Payloads and headers
  • Security and signing
  • Delivery and retries
  • Managing via API

Connect

  • Connectors
  • Integrations

Product

  • Collaboration and chat

Reference

  • Glossary
  • Keyboard shortcuts
  • Module reference

SDK

Python SDK

A typed client for the Atlas REST API, generated from the OpenAPI spec. Works in any Python 3.8+ project with no third-party dependencies.

Language
PythonGoRuby
One REST surface, three idiomatic clients.

Overview

The Atlas Python SDK wraps the same REST surface documented in the API guide. One Client class exposes a method per operation, handles bearer-token auth, and raises a typed error hierarchy on non-2xx responses. It is generated from docs/atlas-openapi.json, so method names track the API exactly and never drift from the reference.

Method naming

Each method is the snake_case of the OpenAPI operationId. Listing tasks (operation atlas_get_tasks) is client.atlas_get_tasks(...). The methods overview maps the common resources.

Install

Published on PyPI. Pin the version to match your API tier in production.

bash
pip install atlas-sdk
bash
poetry add atlas-sdk

Authentication

Every request carries a Personal Access Token as a bearer credential. Tokens start with atlas_pat_ and are scope-bounded and revocable.

python
import os

from atlas_sdk import Client

# Read the token from the environment - never hard-code it in source.
token = os.environ["ATLAS_TOKEN"]

client = Client(token=token)

Scopes are enforced server-side

A token only reaches the routes its scopes allow. A call outside them fails with 403 (insufficient_scope) and raises ForbiddenError - the SDK never widens a token. Mint the narrowest scope set that works and widen only when a call is refused.

Quickstart

One authenticated request, end to end. Set ATLAS_TOKEN in your environment first.

python
import os

from atlas_sdk import Client

client = Client(token=os.environ["ATLAS_TOKEN"])

# List endpoints answer an { "items": [...], "nextCursor": ... } envelope.
page = client.atlas_get_tasks(query={"limit": 5})
for task in page["items"]:
    print(task["id"], task["title"])

Core examples

The everyday task and project operations. Reads answer an { items, nextCursor } envelope; writes take a body mapping.

List tasks with filters

python
# Filters map straight to the REST query string. See the API reference for the
# full set; the server ignores keys it does not recognise.
page = client.atlas_get_tasks(query={
    "projectId": "prj_123",
    "status": "IN_PROGRESS",
    "assigneeId": "usr_42",
    "limit": 50,
})
tasks = page["items"]

Get a task

python
# Single-resource routes carry the id in the path. Every typed method delegates
# to Client._request, so call it directly to fill the id in.
task = client._request("GET", f"/tasks/{task_id}")
print(task["title"], task["status"])

Single-resource routes

The generated atlas_get_tasks_record method targets /tasks/{id} but does not interpolate the id, so fill it through the shared _request transport as shown. The same applies to update and delete.

Create a task

python
created = client.atlas_post_tasks(body={
    "projectId": "prj_123",
    "title": "Review Q3 forecast",
    "priority": "HIGH",
    "dueOn": "2026-05-01T17:00:00Z",
})
print("created", created["id"])

Update a task

python
updated = client._request(
    "PATCH",
    f"/tasks/{task_id}",
    body={"status": "DONE"},
)

Cursor-based pagination

python
def iter_tasks(client, **filters):
    """Yield every task across all pages by following nextCursor."""
    cursor = None
    while True:
        query = {**filters, "limit": 100}
        if cursor:
            query["cursor"] = cursor
        page = client.atlas_get_tasks(query=query)
        for task in page["items"]:
            yield task
        cursor = page.get("nextCursor")
        if not cursor:
            break

for task in iter_tasks(client, projectId="prj_123"):
    print(task["id"])

List projects

python
projects = client.atlas_get_projects(query={"limit": 50})
for project in projects["items"]:
    print(project["id"], project["name"])

Error handling

Every non-2xx response raises a subclass of ApiError. The error carries the HTTP status and the parsed problem+json body.

python
from atlas_sdk import (
    ApiError,
    UnauthorizedError,
    ForbiddenError,
)

try:
    client.atlas_post_tasks(body={"title": ""})
except ForbiddenError as err:
    # 403: the token is missing a scope. The scopes are in the problem body.
    problem = err.body or {}
    print("need scopes:", problem.get("requiredScopes"))
except UnauthorizedError:
    # 401: token invalid or revoked - re-authenticate.
    raise
except ApiError as err:
    # 409 / 422 / 429 and anything else land here with .status set.
    problem = err.body or {}
    print(err.status, problem.get("title"), problem.get("requestId"))
    if err.status == 422:
        for field in problem.get("errors", []):
            print(field["path"], field["message"])
    raise
HTTP statusError classMeaning
400BadRequestErrorMalformed request or query.
401UnauthorizedErrorToken missing, invalid, expired, or revoked.
403ForbiddenErrorToken is missing a required scope.
404NotFoundErrorResource does not exist or is out of tenant.
409, 422, 429ApiError (base)No dedicated class - branch on the status code and read the body.
5xxServerErrorTransient server fault. Safe to retry.

Only the five statuses above get a dedicated class. Conflicts (409), validation failures (422), and rate limits (429) raise the base ApiError, so branch on err.status and read the body. Every error wraps the API's RFC 9457 problem+json envelope:

http
HTTP/1.1 403 Forbidden
Content-Type: application/problem+json
x-request-id: f47ac10b-58cc-4372-a567-0e02b2c3d479

{
  "type": "https://atlas.dev/errors/insufficient-scope",
  "title": "Insufficient scope",
  "status": 403,
  "detail": "This Personal Access Token is missing one of the required scopes: tasks:write.",
  "requiredScopes": ["tasks:write"],
  "grantedScopes": ["tasks:read"],
  "requestId": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

err.body is that JSON object: type, title, status, detail, and a requestId to quote in support tickets. A 422 adds an errors array; a 429 adds a rateLimit object.


Retries and rate limits

Retry 429 and 5xx; never retry 4xx you caused. The 429 body carries the retry delay, so honour it before falling back to backoff.

python
import random
import time

from atlas_sdk import ApiError

RETRYABLE = {429, 500, 502, 503, 504}

def with_retries(call, *, max_attempts=5):
    """Retry on 429 and 5xx with exponential backoff and jitter."""
    for attempt in range(1, max_attempts + 1):
        try:
            return call()
        except ApiError as err:
            if err.status not in RETRYABLE or attempt == max_attempts:
                raise
            problem = err.body or {}
            rate_limit = problem.get("rateLimit") or {}
            # The 429 body carries the authoritative retry delay, in seconds.
            delay = rate_limit.get("retryAfterSec")
            if delay is None:
                delay = min(2 ** (attempt - 1), 30) + random.random()
            time.sleep(delay)

page = with_retries(lambda: client.atlas_get_tasks(query={"limit": 50}))

Back off with jitter

When no retryAfterSec is present, use exponential backoff with a random jitter so a fleet of clients does not retry in lockstep. Cap total attempts - a persistent 429 means you need a higher tier, not more retries.

Idempotency

Every write accepts an Idempotency-Key header. Replaying the same key within 24h returns the original response verbatim - safe retries on flaky networks.

python
import json
import os
import uuid
from urllib import request as urlrequest

from atlas_sdk import Client

class IdempotentClient(Client):
    """The generated client sends a fixed header set; this thin extension adds
    an Idempotency-Key to writes so a retried request replays (24h) instead of
    creating a duplicate."""

    def write(self, method, path, body, *, key):
        req = urlrequest.Request(
            f"{self._base_url}{path}",
            data=json.dumps(body).encode(),
            method=method.upper(),
            headers={
                "Authorization": f"Bearer {self._token}",
                "Content-Type": "application/json",
                "Accept": "application/json",
                "Idempotency-Key": key,
            },
        )
        with urlrequest.urlopen(req, timeout=self._timeout) as resp:
            raw = resp.read()
            return json.loads(raw) if raw else None

client = IdempotentClient(token=os.environ["ATLAS_TOKEN"])

# Generate one key per logical write and reuse it on every retry.
key = str(uuid.uuid4())
task = client.write("POST", "/tasks", {"projectId": "prj_123", "title": "Ship it"}, key=key)

Reuse the key on retry

Generate the key once per logical operation and pass the same value on every retry. A new key is a new operation, so a retried create with a fresh key produces a duplicate. See the REST guide for the full replay semantics.

Configuration

The constructor takes the base URL and a per-request timeout. Override the base URL for staging or a self-hosted deployment.

python
from atlas_sdk import Client

client = Client(
    token=os.environ["ATLAS_TOKEN"],
    base_url="https://api.atlas.app/v1",  # override for staging or self-hosted
    timeout=30.0,                          # seconds, applied per request
)

For a custom User-Agent, request id, or any other header, extend the client as in Idempotency - the transport is a single small method you can wrap.


Methods overview

The common task and project operations. The full set - one method per operation - lives in the API reference.

ResourceRESTSDK method
List tasksGET /tasksatlas_get_tasks(query=...)
Create taskPOST /tasksatlas_post_tasks(body=...)
Get taskGET /tasks/{id}atlas_get_tasks_record *
Update taskPATCH /tasks/{id}atlas_patch_tasks_record *
Delete taskDELETE /tasks/{id}atlas_delete_tasks_record *
List projectsGET /projectsatlas_get_projects(query=...)
Create projectPOST /projectsatlas_post_projects(body=...)

* Single-resource methods exist but send the literal {id} path; fill the id through _request (see Get a task).


Next steps

The SDK is a thin, faithful layer over the REST API. When you need the exact shape of a request or response, go to the source.

  • REST API guideAuth, pagination, idempotency, and rate limits at the protocol level - the contract every SDK speaks./docs/api
  • API referenceEvery operation, request body, and response shape - the source of truth for method arguments./docs/api/reference
  • MCP for AI agentsHand the same operations to an AI agent over the Model Context Protocol./docs/mcp

On this page

  • Overview
  • Install
  • Authentication
  • Quickstart
  • Core examples
  • List tasks
  • Get a task
  • Create a task
  • Update a task
  • Pagination
  • List projects
  • Error handling
  • Retries and rate limits
  • Idempotency
  • Configuration
  • Methods overview
  • Next steps