> ## Documentation Index
> Fetch the complete documentation index at: https://docs.datalyr.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Get onboarding status

> What is set up in this workspace, and what a person still has to do. Every other endpoint reports what the numbers say, which reads the same for a workspace with no traffic and a workspace whose tracking was never installed. This one separates them.

**`first_event_seen_at` is the install signal.** `null` means this workspace has never received an event, ever — it is not a windowed answer and it is not a query that gave up. If the tracking data cannot be read, this endpoint returns `503`. It never reports absence it did not verify.

**`sdk_detected` is a 30-day window** of the transports that delivered events: `web` for the browser snippet, the mobile SDK names, and server-side sources such as `shopify` or `stripe`. It is not attribution source. A workspace with revenue webhooks but no `web` entry has a dead storefront snippet, which is exactly the state `next_steps` names.

**`next_steps` is ordered by dependency**, not severity: installation advice comes before integration advice, because connecting an ad account to a workspace that is tracking nothing accomplishes nothing. An empty checklist is expressed as a single sentence saying so, never as an empty array.

**What an agent cannot do about any of it.** Paying, approving a container script, and minting an API key are all human actions. See [Agent-driven setup](/developer/agent-driven-setup).



## OpenAPI

````yaml /api-reference/read-api.yaml get /onboarding
openapi: 3.1.0
info:
  title: Datalyr API
  version: 1.0.0
  description: >-
    Read analytics, attribution, revenue and workspace data from Datalyr, and
    change conversion rules, trackable links and container-script drafts under
    an explicit write scope.
servers:
  - url: https://api.datalyr.com/v1
security:
  - bearerAuth: []
tags:
  - name: Analytics
  - name: Events and users
  - name: Attribution and revenue
  - name: Workspace
  - name: Signup and onboarding
    description: >-
      The two routes an agent needs to take a prospect from nothing to a working
      install: one to start a signup, one to check what is still missing.


      **Agents sign up. Humans pay.** `POST /signup` is the only unauthenticated
      route in this API, and the only thing it does is send a verification
      email. It does not create an account, a workspace, a plan or an API key,
      and no parameter makes it. The person who reads that email verifies it,
      chooses a plan, and completes an Autumn-hosted Stripe checkout in their
      own browser. Only then does a workspace exist, and only then can a key be
      minted. An agent never handles a payment credential and cannot complete a
      checkout on anyone's behalf.


      **Throttled instead of captcha-gated.** A captcha proves a human is
      present, which is the opposite of this endpoint's premise. `POST /signup`
      is capped per client IP and per email address instead. Exceeding either
      returns `429` with a `Retry-After`; the limiter failing returns `503`, and
      never lets a request through.


      **No account-existence oracle.** An accepted `POST /signup` always answers
      `{"status": "verification_sent"}`, whether or not the address already has
      a Datalyr account. Do not infer anything about the address from the
      response.


      **`GET /onboarding` is the checklist.** It needs an Agent key, so it only
      works once a workspace exists — that is, after payment. Read `next_steps`
      and drive the human through them; re-read it to confirm each one landed.
  - name: Documentation
    description: >-
      Search the published Datalyr documentation. The only routes here that read
      content outside your workspace.
  - name: Exports
    description: >-
      Ask for a file instead of a page. An export runs asynchronously and
      produces a CSV or NDJSON object you download with a temporary link.


      **It is a job, not a response.** `POST /exports` returns `202` with a job
      id and no data — an export can scan a year of events and take minutes,
      which is longer than any HTTP request should live. Poll `GET
      /exports/{exportId}` until `status` is `completed`, then read
      `download.url`.


      **Scope.** `datalyr:read`, the same scope every other read on this page
      needs — including the original per-workspace Agent key. An export returns
      rows you can already fetch through `GET /events` or `GET /users`; it does
      not change anything you configured, so it does not require a write scope.


      **The download link is temporary and so is the file.** `download.url` is a
      signed URL valid for one hour from the moment you polled; poll again for a
      fresh one. The file itself is deleted seven days after the export was
      created (`expires_at`), after which the job reads `expired` and must be
      re-run. Treat the URL as a credential: anyone holding it can download the
      file until it expires.


      **Limits.** One export covers at most 366 days and one million rows (users
      exports: 100,000, the bound of the person-grain query). Hitting the row
      cap sets `truncated: true` on the finished job rather than failing it —
      you get the rows that fit and are told the range is not fully covered. A
      workspace may have five exports queued or running at once.


      **What the files contain.** `events` mirrors `GET /events` column for
      column; `users` mirrors `GET /users`; `attribution` is the channel-grain
      summary from `GET /attribution`, one row per channel plus an
      `unattributed` row. Filters are an allowlist per type — an unrecognized
      filter is rejected rather than ignored, so you never receive more rows
      than you asked for without being told.
  - name: Write API
    description: >-
      Create, change and delete conversion rules and trackable links, reverse
      any of it, and propose container scripts for a human to approve.


      **Two tiers, and the difference is deliberate.** Conversion rules and
      trackable links are direct writes. Container scripts are **propose-only**:
      `POST /scripts/proposals` records a draft and nothing else. No scope
      publishes a script, and there is no endpoint that approves one — an owner
      or admin approves it in the Datalyr dashboard, and only then does the
      script exist. A container script is arbitrary JavaScript injected into
      every page of the customer's site, so a key that could publish one would
      turn a leaked key into a supply-chain compromise rather than a data-access
      problem.


      **Scopes.** These routes need a named API key carrying an explicit write
      scope: `datalyr:write:rules` for conversion rules, `datalyr:write:links`
      for trackable links, `datalyr:propose:scripts` to draft a container
      script. The original per-workspace Agent key does not have them and never
      will — it resolves to `datalyr:read` alone, so it reads every route in
      this spec and writes none of them. Mint a named key with the scopes you
      want; a request without the right one returns `403` with `code:
      insufficient_scope` and the `required_scope` it wanted.


      **Dry run.** Every mutation accepts `dry_run`. A dry run validates the
      request, reads whatever it needs to describe the outcome, and returns the
      exact change it would make — including, for a conversion rule, which
      platform, ad account and pixel the rule would send conversions to. It
      writes nothing: no row, no audit entry, no edge redirect, no idempotency
      record. It defaults to `false` over raw HTTP, because a `DELETE` you sent
      is a `DELETE` you meant. The MCP write tools default it to `true`.


      **Idempotency.** `POST` accepts an `Idempotency-Key` header. The first
      request with a given key is performed and its response kept for 24 hours;
      an identical retry returns that same response with `Idempotent-Replay:
      true` rather than creating a second resource. The record covers the
      request body too, so reusing a key with different content is a new
      request, not a replay. `PATCH` and `DELETE` are keyed on a resource id and
      need no header.


      **Audit and undo.** Every applied write records what changed, who changed
      it and the state it replaced, in the same transaction as the change. `GET
      /audit` lists those entries and `POST /audit/{auditId}/undo` puts the
      resource back. Undo refuses rather than guessing: it will not reverse a
      create (call `DELETE`, which previews and audits like any other write),
      will not run twice on one entry, and will not restore over a resource that
      has since been deleted or re-created.


      **Rate limit.** Mutations spend a separate, tighter budget than reads: 20
      per minute per key, including dry runs.
paths:
  /onboarding:
    get:
      tags:
        - Signup and onboarding
      summary: Get onboarding status
      description: >-
        What is set up in this workspace, and what a person still has to do.
        Every other endpoint reports what the numbers say, which reads the same
        for a workspace with no traffic and a workspace whose tracking was never
        installed. This one separates them.


        **`first_event_seen_at` is the install signal.** `null` means this
        workspace has never received an event, ever — it is not a windowed
        answer and it is not a query that gave up. If the tracking data cannot
        be read, this endpoint returns `503`. It never reports absence it did
        not verify.


        **`sdk_detected` is a 30-day window** of the transports that delivered
        events: `web` for the browser snippet, the mobile SDK names, and
        server-side sources such as `shopify` or `stripe`. It is not attribution
        source. A workspace with revenue webhooks but no `web` entry has a dead
        storefront snippet, which is exactly the state `next_steps` names.


        **`next_steps` is ordered by dependency**, not severity: installation
        advice comes before integration advice, because connecting an ad account
        to a workspace that is tracking nothing accomplishes nothing. An empty
        checklist is expressed as a single sentence saying so, never as an empty
        array.


        **What an agent cannot do about any of it.** Paying, approving a
        container script, and minting an API key are all human actions. See
        [Agent-driven setup](/developer/agent-driven-setup).
      operationId: get-onboarding
      responses:
        '200':
          $ref: '#/components/responses/Onboarding'
        '401':
          $ref: '#/components/responses/Error'
        '503':
          $ref: '#/components/responses/Error'
components:
  responses:
    Onboarding:
      description: Successful response.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/OnboardingResponse'
    Error:
      description: Error response.
      content:
        application/json:
          schema:
            type: object
            properties:
              error:
                type: string
  schemas:
    OnboardingResponse:
      type: object
      additionalProperties: true
      required:
        - workspace
        - tracking
        - connections
        - next_steps
        - complete
      properties:
        workspace:
          type: object
          additionalProperties: true
          properties:
            id:
              type: string
              description: Public workspace ID.
            name:
              type:
                - string
                - 'null'
            created:
              type: boolean
              description: >-
                Always `true`. A workspace row exists only after a paid
                checkout, and an Agent key exists only on a workspace row, so an
                authenticated caller cannot observe `false`.
            created_at:
              type:
                - string
                - 'null'
              format: date-time
            active:
              type: boolean
              description: False once the workspace is suspended or scheduled for deletion.
            plan:
              type:
                - string
                - 'null'
              description: >-
                Plan id from billing. `null` means the billing lookup did not
                answer — it does not mean free.
        tracking:
          type: object
          additionalProperties: true
          properties:
            first_event_seen_at:
              type:
                - string
                - 'null'
              format: date-time
              description: >-
                The first event this workspace ever received, over all time.
                `null` means never.
            last_event_seen_at:
              type:
                - string
                - 'null'
              format: date-time
              description: >-
                Most recent event in the last 30 days. Null when the workspace
                has been silent that long.
            sdk_detected:
              type: array
              description: Distinct event transports seen in the last 30 days, sorted.
              items:
                type: string
            events_last_30d:
              type: integer
        connections:
          type: array
          description: One entry per configured integration, whatever its health.
          items:
            type: object
            additionalProperties: true
            properties:
              platform:
                type: string
                description: Provider key, such as `meta`, `google`, `shopify`.
              status:
                type: string
                description: >-
                  `active` is healthy. Anything else needs a person to reconnect
                  it, and appears in `next_steps` by name.
              error:
                type:
                  - string
                  - 'null'
              last_synced_at:
                type:
                  - string
                  - 'null'
                format: date-time
        next_steps:
          type: array
          description: >-
            Ordered actions for a person to take. Never empty: a workspace with
            nothing outstanding gets one sentence saying so.
          items:
            type: string
        complete:
          type: boolean
          description: True when every source answered. A partial read returns 503 instead.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Datalyr Agent key (dk_agent_…)

````