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

# Quickstart

> Register an App, install it on the sandbox, and send your first message

This walks through the shortest path from nothing to a message landing in a
Workchats DM: register your App, install it on the sandbox company, confirm
the token works, then send.

All of this runs against the staging sandbox
(`https://public-api.staging.workchats.com`), never a real company. See
[Sandbox](/getting-started/sandbox) for how to get access.

<Steps>
  <Step title="Register your App">
    Apps are registered by Octogle, not self-serve. Tell your Octogle contact:

    * the App's name (shown on the consent screen and on every message it sends)
    * the OAuth redirect URI your backend will receive `code` on

    You get back a `client_id` and a `client_secret`, and access to a sandbox
    company with a staging App registration. Tokens issued in the sandbox are
    prefixed `wc_bot_test_` rather than `wc_bot_live_`, so they're easy to tell
    apart in logs.

    Keep `client_secret` server-side. It's a credential, not something to ship
    in a browser or mobile build.
  </Step>

  <Step title="Install on the sandbox">
    Send the company admin to the authorize URL your Octogle contact gives you
    for the sandbox (a staging counterpart of
    `https://app.workchats.com/oauth/authorize`), with:

    ```
    ?client_id=<your client_id>
    &redirect_uri=<your registered redirect_uri, exactly>
    &scope=users:read users:read.email conversations:read messages:write
    &state=<opaque, single-use, generated by you>
    ```

    The admin sees a consent screen naming your App and the scopes it's asking
    for, and picks which groups and channels the bot may join. On approval,
    Workchats redirects to `redirect_uri?code=<code>&state=<state>`.

    Exchange the code for a token:

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://public-api.staging.workchats.com/oauth/token \
        -u "$CLIENT_ID:$CLIENT_SECRET" \
        -H "Content-Type: application/json" \
        -d '{
          "grant_type": "authorization_code",
          "code": "'"$CODE"'",
          "redirect_uri": "'"$REDIRECT_URI"'"
        }'
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://public-api.staging.workchats.com/oauth/token", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: "Basic " + Buffer.from(`${process.env.CLIENT_ID}:${process.env.CLIENT_SECRET}`).toString("base64"),
        },
        body: JSON.stringify({
          grant_type: "authorization_code",
          code: process.env.CODE,
          redirect_uri: process.env.REDIRECT_URI,
        }),
      });
      const token = await res.json();
      console.log(token.access_token); // wc_bot_test_...
      ```
    </CodeGroup>

    ```json 200 theme={null}
    {
      "access_token": "wc_bot_test_0Xa9…",
      "token_type": "Bearer",
      "scope": "users:read users:read.email conversations:read messages:write",
      "workspace": { "id": "cmp_3f0c1b9e-5d0a-4a57-9a53-2f4f1c9d6b10", "name": "Corpwise" },
      "bot_user_id": "usr_8a1d2c3e-4b5f-4a6b-8c7d-9e0f1a2b3c4d"
    }
    ```

    This token doesn't expire and there's no refresh token — save it now.
    Reinstalling later keeps the same bot user but issues a new token, and the
    old one stops working (`401 token_revoked`). Replace the stored token
    whenever you finish a new install.
  </Step>

  <Step title="Confirm the token works">
    <CodeGroup>
      ```bash curl theme={null}
      curl https://public-api.staging.workchats.com/v1/auth/test \
        -H "Authorization: Bearer $ACCESS_TOKEN"
      ```

      ```javascript Node theme={null}
      const res = await fetch("https://public-api.staging.workchats.com/v1/auth/test", {
        headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` },
      });
      console.log(await res.json());
      ```
    </CodeGroup>

    ```json 200 theme={null}
    {
      "ok": true,
      "workspace": { "id": "cmp_3f0c1b9e-5d0a-4a57-9a53-2f4f1c9d6b10", "name": "Corpwise", "domain": "corpwise.ae" },
      "bot_user_id": "usr_8a1d2c3e-4b5f-4a6b-8c7d-9e0f1a2b3c4d",
      "scopes": ["users:read", "users:read.email", "conversations:read", "messages:write"]
    }
    ```
  </Step>

  <Step title="Send a DM">
    Look up a recipient by email, then send:

    <CodeGroup>
      ```bash curl theme={null}
      curl "https://public-api.staging.workchats.com/v1/users/lookup?email=connor@corpwise.ae" \
        -H "Authorization: Bearer $ACCESS_TOKEN"

      curl -X POST https://public-api.staging.workchats.com/v1/messages \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: quickstart-1:usr_9a1d2c3e" \
        -d '{
          "to": { "type": "user", "id": "usr_9a1d2c3e-4b5f-4a6b-8c7d-9e0f1a2b3c4d" },
          "text": "Hello from my App"
        }'
      ```

      ```javascript Node theme={null}
      const lookup = await fetch(
        "https://public-api.staging.workchats.com/v1/users/lookup?email=connor@corpwise.ae",
        { headers: { Authorization: `Bearer ${process.env.ACCESS_TOKEN}` } }
      );
      const { user } = await lookup.json();

      const sent = await fetch("https://public-api.staging.workchats.com/v1/messages", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
          "Content-Type": "application/json",
          "Idempotency-Key": `quickstart-1:${user.id}`,
        },
        body: JSON.stringify({
          to: { type: "user", id: user.id },
          text: "Hello from my App",
        }),
      });
      console.log(await sent.json());
      ```
    </CodeGroup>

    ```json 201 theme={null}
    {
      "ok": true,
      "message": {
        "id": "dmsg_4e2d1c0b-9a8f-4e7d-8c6b-5a4f3e2d1c0b",
        "conversation_id": "dm_77a1b2c3-d4e5-4f60-8a7b-9c0d1e2f3a4b",
        "thread_id": null,
        "created_at": "2026-09-28T11:05:12Z"
      }
    }
    ```

    The `Idempotency-Key` matters as soon as you retry on a timeout. See
    [Idempotency](/guides/idempotency) before you write real send logic.
  </Step>
</Steps>

## Next

* [Sending messages](/guides/sending-messages) covers `blocks`, edits and
  deletes.
* [Errors](/guides/errors) lists every code you can get back.
* [Concepts](/getting-started/concepts) explains Apps, installations and
  scopes in more depth.
