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

    Developer

    REST API

    Atlas exposes a hand-curated, hardened public REST surface under /v1/. One bearer header gets you in. This guide covers auth, success and error responses (every status code with an example body), rate limits, idempotency, pagination, and webhooks, and points you at the interactive reference for every route.

    Start at the interactive reference

    Every endpoint, parameter, scope, request body, and status code, rendered live from the OpenAPI spec with copyable request samples and a Try It console. This guide explains the concepts; the reference is the source of truth for each route.

    Open the API reference

    Browse every endpoint, parameter, schema, and error in a searchable three-column reference. It is CSP-safe and always reflects the live spec, so it never drifts from the deployed server.

    Rendered live from https://api-atlas.wrxstack.com/v1/openapi.json.


    Quickstart

    Go from nothing to your first authenticated request in about a minute.

    1. 1

      Mint a Personal Access Token

      Open Settings, then API access, and click New token. Pick the narrowest scope set (start with tasks:read). The token is shown once and starts with atlas_pat_.
    2. 2

      Send your first request

      Pass the token as a bearer header and read your tasks. This lists the first 50 tasks in your workspace.
      curl -H "Authorization: Bearer atlas_pat_REPLACE_ME" \
        https://api-atlas.wrxstack.com/v1/tasks?limit=50
    3. 3

      Go deep in the reference

      Open the interactive reference to see every route, its parameters, and its full response shape, each with a request sample you can copy.

    Keep tokens narrow

    Start with read-only scopes and widen only when a call needs it. You can revoke and rotate a token at any time without touching the rest of your integration.

    Authentication

    Every /v1 request carries a bearer token. The same header accepts a Personal Access Token (atlas_pat_...) or a session JWT. PATs are the recommended path for server-to-server use because they are scope-bounded and revocable independently of any user session.

    http
    Authorization: Bearer atlas_pat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Full authentication guide

    This section is the quick version. For the complete story (minting, rotating, and revoking PATs, plus the OAuth 2.0 authorization-code and PKCE flow, refresh, introspection, revocation, and discovery), see Authentication. It also has a PAT vs OAuth comparison to help you pick.

    Scopes are enforced per request

    A PAT authenticates but a call still fails with 403 (insufficient_scope) if the token is missing the scope that route requires. The problem body lists requiredScopes and grantedScopes so you know exactly what to add. Session-JWT calls bypass the scope gate (the session is implicitly all-scopes). See the 403 example below.

    Success responses

    Successful calls return application/json (never problem+json). Reads answer 200; creates answer 201 with a Location header. List endpoints are always an { items, nextCursor } envelope, so a single item and a thousand share one shape. Every response also carries an x-request-id header for support tracing.

    200

    OK: GET /v1/tasks (paginated list)

    Collection reads return the requested page under items plus an opaque nextCursor (null on the last page). Rate-limit headers ride along on every 2xx.

    http
    HTTP/1.1 200 OK
    Content-Type: application/json
    x-request-id: 3f1a9c02-7d4e-4b1a-9c8f-2a6b1e0d5f77
    X-RateLimit-Class: read
    X-RateLimit-Limit: 300
    X-RateLimit-Remaining: 299
    
    {
      "items": [
        {
          "id": "tsk_01HW3T2K8X9Y2VPRZGQX9NDY1F",
          "projectId": "prj_01HW3S9QZ4M0V6B7C8D9E0F1G2",
          "title": "Review Q3 forecast",
          "status": "TODO",
          "priority": "HIGH",
          "dueOn": "2026-05-01T17:00:00Z",
          "assigneeId": "usr_01HW3RA2B3C4D5E6F7G8H9J0K1",
          "version": 1,
          "createdAt": "2026-04-18T09:12:44Z",
          "updatedAt": "2026-04-18T09:12:44Z"
        }
      ],
      "nextCursor": "eyJpZCI6InRza18wMUhXM1QifQ=="
    }
    201

    Created: POST /v1/tasks (the created resource)

    Creates return the full resource, including its server-assigned id, version, and timestamps, plus a Location header. Replaying the same Idempotency-Key returns this exact 201 again (see Idempotency).

    http
    HTTP/1.1 201 Created
    Content-Type: application/json
    Location: /v1/tasks/tsk_01HW3T2K8X9Y2VPRZGQX9NDY1F
    x-request-id: 9b2c4d6e-1f38-4a5b-8c7d-0e9f1a2b3c4d
    
    {
      "id": "tsk_01HW3T2K8X9Y2VPRZGQX9NDY1F",
      "projectId": "prj_01HW3S9QZ4M0V6B7C8D9E0F1G2",
      "title": "Review Q3 forecast",
      "status": "TODO",
      "priority": "HIGH",
      "dueOn": "2026-05-01T17:00:00Z",
      "assigneeId": null,
      "version": 1,
      "createdAt": "2026-04-18T09:12:44Z",
      "updatedAt": "2026-04-18T09:12:44Z"
    }

    Errors

    Every non-2xx response uses one consistent envelope: RFC 9457 / RFC 7807 problem+json. Always branch on Content-Type (success is application/json, any error is application/problem+json) and read status from the body, not just the status line.

    The error envelope

    Five members are always present. The type URI is the stable, machine-readable identifier for the problem class (branch on it, not on the human copy); detail is the human message for this specific request. Every type comes from one registry: one shared type per kind of failure, such as not-found, forbidden and conflict, with the specific case in code. Specific problem types add extension members, called out per status below.

    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"
    }
    FieldTypeMeaning
    typestring (uri)Stable identifier for the problem class, always https://atlas.dev/errors/<slug>. A module-specific not found, forbidden or conflict is served as the shared type, with its own name in code.
    titlestringShort, human summary of the problem type. Same for every instance of that type.
    statusintegerThe HTTP status, mirrored in the body so it survives logging and proxies.
    detailstringHuman explanation specific to this request. Safe to surface to developers.
    requestIdstringCorrelation id, also returned in the x-request-id header. Quote it in support requests.

    Extension members you will meet below: errors[] (422 validation, with a path, message, and code per field), requiredScopes / grantedScopes (403 insufficient scope), and rateLimit (429).

    At a glance

    StatusWhen you see itRetry?
    400Malformed request or a value a storage constraint rejects.No, fix the request
    401Missing, unknown, revoked, or expired token.No, re-auth
    403Token lacks the required scope, or origin is blocked.No, widen the token
    404Resource missing, or invisible to your tenant.No
    409Unique collision, stale If-Match version, or a connector that is not connected.Yes, re-read then retry; not for a connector
    422Body parsed but failed field validation.No, fix the fields
    429Rate bucket exhausted. Retry-After header set.Yes, after Retry-After
    500Server fault. Body is generic; requestId traces it.Yes, with backoff

    Every status, with an example

    400

    Bad Request

    The request itself is malformed: unparseable JSON, a value that a storage constraint rejects (for example a string that is too long), or a required value missing at the persistence layer. Field-level schema failures come back as 422 instead.

    http
    HTTP/1.1 400 Bad Request
    Content-Type: application/problem+json
    x-request-id: 1a2b3c4d-5e6f-4708-8192-a3b4c5d6e7f8
    
    {
      "type": "https://atlas.dev/errors/bad-request",
      "title": "Bad request",
      "status": 400,
      "detail": "One or more values are invalid.",
      "requestId": "1a2b3c4d-5e6f-4708-8192-a3b4c5d6e7f8"
    }
    401

    Unauthorized

    No bearer token, or the token is empty, unknown, revoked, or expired. Mint or rotate the PAT and retry. The detail narrows the cause (for example Personal access token revoked). A session token that has only expired answers with type https://atlas.dev/errors/token-expired, so refresh it; every other case is https://atlas.dev/errors/unauthenticated.

    http
    HTTP/1.1 401 Unauthorized
    Content-Type: application/problem+json
    x-request-id: 7c9e2f14-3a5b-4d6e-8f70-1b2c3d4e5f60
    
    {
      "type": "https://atlas.dev/errors/unauthenticated",
      "title": "Not signed in",
      "status": 401,
      "detail": "Invalid or expired access token",
      "requestId": "7c9e2f14-3a5b-4d6e-8f70-1b2c3d4e5f60"
    }
    403

    Forbidden

    The token is valid but not allowed to do this. The common case is a PAT missing a required scope; the body then carries requiredScopes and grantedScopes so you can widen the token. A browser origin outside the CORS allowlist also 403s. Any other refusal uses type https://atlas.dev/errors/forbidden.

    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"
    }
    404

    Not Found

    The resource does not exist, or it exists in another tenant your token cannot see. Atlas does not distinguish the two on purpose: a cross-tenant id is indistinguishable from a missing one, so ids never leak across tenants.

    http
    HTTP/1.1 404 Not Found
    Content-Type: application/problem+json
    x-request-id: 2d4f6a8c-0e13-4257-9b8d-6f0a1c3e5d70
    
    {
      "type": "https://atlas.dev/errors/not-found",
      "code": "task-not-found",
      "title": "Task not found",
      "status": 404,
      "detail": "No task with id tsk_9 in this workspace.",
      "requestId": "2d4f6a8c-0e13-4257-9b8d-6f0a1c3e5d70"
    }
    409

    Conflict

    The write collides with existing state: a unique value already in use, an If-Match version that is now stale (optimistic concurrency), or a transient write conflict. Re-read the resource, merge, and retry with the fresh version. A connector route also answers 409 when the service it reaches is not connected (connector-not-connected) or its connection has expired and must be made again (connector-reconnect-required). Retrying does not help there: an administrator connects the service in Settings, under Integrations. The body names it in connectorId. Your token is fine in both cases, so do not refresh it.

    http
    HTTP/1.1 409 Conflict
    Content-Type: application/problem+json
    x-request-id: 8e0c2a46-9f7b-4d31-a0c5-2e4f6a8b0d13
    
    {
      "type": "https://atlas.dev/errors/conflict",
      "title": "Conflict",
      "status": 409,
      "detail": "The operation conflicted with a concurrent change. Please retry.",
      "requestId": "8e0c2a46-9f7b-4d31-a0c5-2e4f6a8b0d13"
    }
    422

    Unprocessable Entity

    The body parsed but failed schema validation. The response adds an errors array with one entry per failing field: its path, a human message, and a machine code. Fix the listed fields and resend.

    http
    HTTP/1.1 422 Unprocessable Entity
    Content-Type: application/problem+json
    x-request-id: 5b7d9f11-2c4e-4638-8a0b-1d3f5a7c9e02
    
    {
      "type": "https://atlas.dev/errors/validation",
      "title": "Validation failed",
      "status": 422,
      "detail": "One or more fields failed validation",
      "errors": [
        {
          "path": "title",
          "message": "String must contain at least 1 character(s)",
          "code": "too_small"
        },
        {
          "path": "priority",
          "message": "Invalid enum value. Expected 'LOW' | 'MEDIUM' | 'HIGH'",
          "code": "invalid_enum_value"
        }
      ],
      "requestId": "5b7d9f11-2c4e-4638-8a0b-1d3f5a7c9e02"
    }
    429

    Too Many Requests

    You exhausted a per-tenant rate bucket (read, write, or ai). The response includes a Retry-After header (seconds) plus the X-RateLimit-* family, and a rateLimit object in the body. Honour Retry-After; it is authoritative.

    http
    HTTP/1.1 429 Too Many Requests
    Content-Type: application/problem+json
    Retry-After: 12
    X-RateLimit-Class: write
    X-RateLimit-Limit: 60
    X-RateLimit-Remaining: 0
    X-RateLimit-Reset: 1746123456
    x-request-id: 0c1d2e3f-4a5b-4c6d-8e9f-a0b1c2d3e4f5
    
    {
      "type": "https://atlas.dev/errors/rate-limited",
      "title": "Rate limit exceeded",
      "status": 429,
      "detail": "Too many write requests for this tenant. Retry in 12s.",
      "rateLimit": {
        "class": "write",
        "limit": 60,
        "windowMs": 60000,
        "retryAfterSec": 12,
        "tier": "pro"
      },
      "requestId": "0c1d2e3f-4a5b-4c6d-8e9f-a0b1c2d3e4f5"
    }
    500

    Internal Server Error

    Something failed on our side. The body is deliberately generic (it never leaks internal detail) but the requestId (also in x-request-id) lets support trace the exact failure. Safe to retry with exponential backoff (300ms, 800ms, 2s).

    http
    HTTP/1.1 500 Internal Server Error
    Content-Type: application/problem+json
    x-request-id: a1b2c3d4-e5f6-4708-9a0b-1c2d3e4f5061
    
    {
      "type": "https://atlas.dev/errors/internal",
      "title": "Internal Server Error",
      "status": 500,
      "detail": "An unexpected error occurred.",
      "requestId": "a1b2c3d4-e5f6-4708-9a0b-1c2d3e4f5061"
    }

    Rate limits

    Every route is classified as read, write, or AI, and each class has its own limit of requests a minute for each workspace. Your plan sets the limits below, and Atlas can set different ones for your workspace.

    ClassDefault ceilingApplies to
    read300/mGET requests on /v1/* (excluding /v1/ai/*)
    write60/mPOST/PATCH/PUT/DELETE on /v1/*
    ai20/mAny /v1/ai/* call (read or write)

    Requests a minute by plan

    PlansRead requests a minuteWrite requests a minuteAI requests a minute
    Free3006020
    Pro, Team, and Business1,20030060
    Enterprise6,0001,500300

    A limit belongs to the workspace and is shared by every API instance, so spreading calls across servers or tokens does not raise it. A limit Atlas sets for a workspace replaces the plan limit.

    Rate limit headers

    Every answer to a limited call carries these headers. When a limit is reached, Atlas answers 429 with a Retry-After header in seconds. Wait that long before you try again.

    HeaderMeaning
    X-RateLimit-ClassThe class the call was counted in: read, write, ai.
    X-RateLimit-LimitThe requests allowed in the window.
    X-RateLimit-RemainingThe requests left in the current window.
    X-RateLimit-ResetWhen the window starts again, in seconds since 1 January 1970, in UTC.
    X-RateLimit-Window-MsThe length of the window in milliseconds.
    X-RateLimit-TierThe rate limit tier of the workspace: free, pro, enterprise.

    Monthly quota

    Calls made with a personal access token or an OAuth token count toward a monthly quota. Your plan sets the quota, and Atlas can set a different one for your workspace. The count starts again at the beginning of each calendar month, in UTC. Calls from the signed-in Atlas app are not counted.

    When the quota has a cap, each answer to a token call carries X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset. X-Quota-Reset gives the moment the count starts again, in seconds since 1 January 1970, in UTC.

    When the quota is spent, Atlas answers 429 with the problem type https://atlas.dev/errors/quota-exceeded, a Retry-After header, and a quota member that gives the period, the limit, the calls used, and when the count starts again.

    http
    HTTP/1.1 429 Too Many Requests
    Content-Type: application/problem+json
    Retry-After: 432000
    X-Quota-Limit: 100000
    X-Quota-Remaining: 0
    X-Quota-Reset: 1793491200
    x-request-id: 6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d
    
    {
      "type": "https://atlas.dev/errors/quota-exceeded",
      "title": "Monthly API quota reached",
      "status": 429,
      "detail": "This workspace has used its 100000 API calls for the month. The count starts again at 2026-11-01T00:00:00.000Z.",
      "quota": {
        "period": "month",
        "limit": 100000,
        "used": 100001,
        "resetAt": "2026-11-01T00:00:00.000Z"
      },
      "requestId": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d"
    }

    Read your limits

    Atlas can adjust the limits of a single workspace: the requests a minute for each class, the monthly quota, the daily AI spend cap, and how its webhooks are retried.

    Read the limits in force for your workspace with GET /v1/workspace/limits. Owners and administrators can call it, and so can a token with the workspace:read scope. The answer gives each value in force, the plan default, any value Atlas has set, and the calls counted this month.


    Idempotency

    Every POST route accepts an Idempotency-Key header. Replaying the same key (same tenant) within 24h returns the original 2xx response verbatim, including the resource id. This is what makes retries safe on flaky networks.

    bash
    curl -X POST https://api-atlas.wrxstack.com/v1/tasks \
      -H "Authorization: Bearer atlas_pat_REPLACE_ME" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: $(uuidgen)" \
      -d '{
        "projectId": "prj_...",
        "title": "Review Q3 forecast",
        "priority": "HIGH",
        "dueOn": "2026-05-01T17:00:00Z"
      }'

    Use a fresh UUID per logical operation: uuidgen on the shell, randomUUID() in Node, or any stable hash you can re-derive on retry. Do not reuse a key for different requests; Atlas matches on the key, not the body.


    Pagination

    Every list endpoint returns { items, nextCursor }, never a bare array. Pass nextCursor back as ?cursor= to get the following page; it is null on the last page, and a list that returns all of its rows at once always answers null. Cursors are opaque, so never parse or modify them.

    bash
    # First page
    curl -H "Authorization: Bearer atlas_pat_..." \
      "https://api-atlas.wrxstack.com/v1/tasks?limit=50"
    # Response: { "items": [...], "nextCursor": "eyJpZCI6Li4ufQ==" }
    
    # Next page
    curl -H "Authorization: Bearer atlas_pat_..." \
      "https://api-atlas.wrxstack.com/v1/tasks?limit=50&cursor=eyJpZCI6Li4ufQ=="

    limit is 1 to 100, 50 by default. A list that can be sorted takes sort, naming one of the fields the operation lists, and order=asc or order=desc; the cursor belongs to that sort, so start again without a cursor when you change it. A cursor that cannot be read, or belongs to another sort, is refused with 422. Rows are ordered by the sort field and then by id, so a row deleted between two requests never ends the list early.


    Versioning and changes

    The API is changed in place, with no version header and no old names kept as aliases. Every change is listed in the changelog on the day it ships, with the old shape, the new shape, and the operations it affects. A webhook delivery carries the apiVersion of its envelope, so a receiver can tell which shape it is reading.

    To stay safe, ignore fields you do not recognise, branch on the problem type rather than on its wording, and read the developer notes in the changelog before you upgrade an integration. Changelog


    Webhooks

    Subscribe an address with /v1/webhooks. Atlas signs every delivery with HMAC-SHA256 and, when a delivery fails, tries again after 30s, 2m, 10m, 30m, 2h, 8h, 24h.

    http
    POST https://your.app/atlas-webhook
    content-type: application/json
    user-agent: Atlas-Webhooks/2
    atlas-signature: t=1790933412,v1=<64 hexadecimal characters>
    atlas-timestamp: 1790933412
    atlas-event-id: evt_cm1b7x0d80000lq8zf2a1k9e3
    atlas-event-type: task.created
    atlas-delivery-id: cm1b7x2k40003lq8z5d0v9h2e
    atlas-subscription-id: cm1a4r9t20001lq8z3c7m6n1p

    Verify the Atlas-Signature header on every delivery before you trust it. The dedicated Webhooks guide covers events, payloads and headers, signature verification in five languages, and the full retry schedule end to end.


    SDKs and clients

    The official TypeScript client mirrors the REST surface 1:1. For AI agents, the MCP server wraps the same endpoints in a model-friendly tool catalogue.

    typescript
    import { createAtlasClient } from '@atlas/client';
    
    const atlas = createAtlasClient({
      baseUrl: 'https://api-atlas.wrxstack.com',
      // PATs are passed verbatim, no refresh logic needed.
      getAccessToken: () => process.env.ATLAS_API_KEY ?? null,
    });
    
    const { items } = await atlas.tasks.list({ limit: 50 });
    const created = await atlas.tasks.create({
      projectId: 'prj_...',
      title: 'Review Q3 forecast',
      priority: 'HIGH',
    });

    Building an AI agent integration? See the MCP setup guide for Claude Desktop, Cursor, and Cline configuration.


    Downloads

    All three artefacts are generated server-side from the same hand-written OpenAPI 3.1 source, so nothing is ever stale.

    • OpenAPI 3.1 specMachine-readable spec with x-required-scopes, x-ratelimit-class, and x-rate-limits extensions.openapi.json
    • Postman collectionPostman v2.1, grouped by tag, with bearer auth, Idempotency-Key, and sample bodies pre-wired.postman.json
    • Postman environmentCompanion environment template. Paste your PAT into apiKey and you are sending real requests.environment.json

    FAQ

    Can I use a session cookie or JWT instead of a PAT?
    Yes, the bearer header accepts either. JWT-authenticated calls bypass the scope gate (the session is implicitly all-scopes). PATs are still recommended for server-to-server because they are tenant-scoped, scope-narrowed, and revocable independently of any user session.
    Why does my POST occasionally return the same id twice?
    You sent the same Idempotency-Key twice within 24h. That is by design: the second call returns the original 2xx response so you do not double-create. Use a fresh UUID for each logical create.
    How do I update a task without overwriting concurrent edits?
    Pass the task's current version field as If-Match: <version>. If the task changed in the meantime you get a 409 with type https://atlas.dev/errors/conflict and code version-conflict; re-read, merge, and retry.
    Is there a sandbox or staging environment?
    Self-host Atlas with a separate database for a sandbox. The same OpenAPI spec applies; just point your PAT-minting client and baseUrl at the sandbox URL.
    How do I get notified when the spec changes?
    Watch the release notes; every public-API change is documented there. The spec also bumps info.version on breaking changes.

    On this page

    • Interactive reference
    • Quickstart
    • Authentication
    • Success responses
    • Errors
    • Rate limits
    • Idempotency
    • Pagination
    • Versioning and changes
    • Webhooks
    • SDKs and clients
    • Downloads
    • FAQ