AtlasWork, planned itself.

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

    System status
    • SSO
    • SCIM
    • Two-factor sign-in
    • Audit log

    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

    You are in control of your cookies

    Atlas uses strictly necessary cookies to keep you signed in. With your consent, we add anonymized product analytics, conversion attribution, and remembered preferences. Change your mind any time at /privacy/cookies.

    Off until you agree · Change it any time

    • Necessaryalways on
    • Analyticsopt-in
    • Marketingopt-in
    • Preferencesopt-in
    Skip to documentation
    Docs
    Back to Atlas

    Start here

    • Overview

    Developer

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

    Webhooks

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

    Connect

    • Connectors
    • Integrations

    Product

    • Collaboration and chat
    • Signing in and security
    • Client portal

    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 the OpenAPI document the API serves at /v1/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 getTasks) is client.get_tasks(...). The methods overview maps the common resources.

    Install

    Atlas does not publish this client to PyPI. Download the single file from Atlas itself; it needs nothing beyond the standard library.

    bash
    # Download the single-file client next to your code.
    curl -fsSLO https://atlas.wrxstack.com/sdk/python/atlas_sdk.py
    bash
    # Check it arrived intact.
    curl -fsSLO https://atlas.wrxstack.com/sdk/python/atlas_sdk.py.sha256
    shasum -a 256 -c atlas_sdk.py.sha256

    Do not install atlas-sdk from PyPI

    Any package named atlas-sdk on PyPI is not published by Atlas. Use only the file served from atlas.wrxstack.com, and download it again when the API changes.

    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 client never widens a token. Create a token with the narrowest scopes 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.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.get_tasks(query={
        "projectId": "prj_123",
        "status": "IN_PROGRESS",
        "assigneeId": "usr_42",
        "limit": 50,
    })
    tasks = page["items"]

    Get a task

    python
    # Path parameters are the leading arguments, filled in and URL-encoded.
    task = client.get_tasks_by_id(task_id)
    print(task["title"], task["status"])

    Path parameters

    Every route with a path parameter takes it as a leading argument: get_tasks_by_id(task_id) calls /v1/tasks/{id} with the id URL-encoded into the path. The same holds for update and delete.

    Create a task

    python
    created = client.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.patch_tasks_by_id(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.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.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.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.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", "/v1/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 a staging server.

    python
    from atlas_sdk import Client
    
    client = Client(
        token=os.environ["ATLAS_TOKEN"],
        base_url="https://api-atlas.wrxstack.com",  # the default; override for staging
        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 /v1/tasksget_tasks(query=...)
    Create taskPOST /v1/taskspost_tasks(body=...)
    Get taskGET /v1/tasks/{id}get_tasks_by_id(id)
    Update taskPATCH /v1/tasks/{id}patch_tasks_by_id(id, body=...)
    Delete taskDELETE /v1/tasks/{id}delete_tasks_by_id(id)
    List projectsGET /v1/projectsget_projects(query=...)
    Create projectPOST /v1/projectspost_projects(body=...)

    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