> ## 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.

# Build an App for your own company

> Register an App in the company admin console that only your company can install

<Info>Dev now; Staging and Production next release.</Info>

<Warning>
  The **Apps** screen in the admin console is not on Dev yet. It arrives on
  Dev with the next admin console release. The steps on this page that
  happen in the console can't be done until then.
</Warning>

Your company can build its own App, for example a script that posts sales
alerts into a channel, without asking Octogle to register it. A company
admin registers the App in the admin console and gets its credentials there.
Only your company can install it.

A company App uses the same OAuth install, bot user, token, scopes, rate
limits and callbacks as any other App, and calls the same API. Everything
else in these docs applies to it.

## Who can register an App

Company admins with the `apps.manage` permission. The company `admin` role
and the super admin have it. A custom role can't be given it.

Any company admin can approve the install on the consent screen, as for
any App.

## How a company App differs from an Octogle App

Octogle registers Apps that any company can install, such as Leadey. Your
company registers its own.

| | App registered by Octogle | App your company registers |
| - | - | - |
| Who registers it | Octogle, on request | Your company admins, in the admin console |
| Who can install it | Any company | Only your company |
| Where its settings, secrets and status are managed | By Octogle | **Apps** in your admin console |
| Name | Unique among Octogle's Apps | Unique within your company |
| Sandbox | A staging sandbox company Octogle sets up | None. Install it in your own company |

To any other company, your App doesn't exist. Its admins get the same
error on the consent screen as for an unknown `client_id`
(`invalid_client`), so they can't install it. If an admin of several
companies opens your install link while another company is selected, they
get that error too. They can switch to your company in Workchats and open
the link again.

## Where things are

| | Dev | Staging | Production |
| - | - | - | - |
| Admin console | `https://admin.dev.workchats.com` | `https://admin.staging.workchats.com` | `https://admin.workchats.com` |
| Consent screen | `https://app.dev.workchats.com/oauth/authorize` | `https://app.staging.workchats.com/oauth/authorize` | `https://app.workchats.com/oauth/authorize` |
| API | `https://public-api.dev.workchats.com` | `https://public-api.staging.workchats.com` | `https://public-api.workchats.com` |

The examples below use Production. Until company Apps reach Production, use
`https://app.dev.workchats.com` and `https://public-api.dev.workchats.com`
instead.

## Register the App

In the admin console, open **Apps** and choose **Register an app**. Fill in:

| Field | Rules |
| - | - |
| Name | Required, at most 255 characters, unique within your company, ignoring case. People see it on the consent screen and on every message your bot sends |
| Description | Optional |
| Redirect URIs | At least one. Each is an absolute `https://` URL with no user name, password or `#fragment`. The consent screen redirects only to a URI that matches one of these exactly |
| Callback URL | Optional, an absolute `https://` URL of at most 255 characters with no user name, password or `#fragment`. Workchats sends [callbacks](/guides/callbacks-and-signature-verification) here. Without one, your App gets none |
| Scopes | At least one. Any of the seven on [Scopes](/reference/scopes). An install can ask for these scopes and no others |

You can edit every field later. An edit that changes nothing is not
recorded.

## Save the secrets

Registering shows three values:

* **Client ID**, `app_…`. Not secret. It's always shown on the App's page,
  and `GET /v1/auth/test` returns it as `app_id`.
* **Client secret**, `wc_cs_…`. Your App sends it with the `client_id` to
  exchange an install code for a token.
* **Signing secret**, `whsec_…`. Your App uses it to
  [verify callbacks](/guides/callbacks-and-signature-verification).

The client secret and the signing secret are shown once, in this dialog.
Workchats can't show them again. Store them where your App reads its
configuration before you close the dialog. If you lose one, rotate it.

## Install it in your company

A company admin installs the App through the consent screen, the same
[OAuth install](/guides/oauth-install) as any App. Send the admin to:

```
https://app.workchats.com/oauth/authorize
  ?client_id=<your client_id>
  &redirect_uri=<one of your redirect URIs, exactly>
  &scope=<scopes separated by spaces, all allowed on the App>
  &state=<opaque, single-use, generated by you>
```

URL-encode each value. The admin picks the groups and channels the bot
joins and approves. Workchats redirects to `redirect_uri` with `code` and
`state`, and your App exchanges the code for a bot token on
`POST /oauth/token`.

Each App has one installation in your company, and its bot user. To change
the bot's groups, its push setting, or to uninstall it, a company admin
uses **Settings → Apps** in Workchats, as for any App.

### Install without a web server

A script or a scheduled job has no web server to receive the redirect. Use
a static page that shows the code instead, then exchange the code yourself
with `curl`. There's no install button in the admin console.

1. Host this page at an `https://` address your company controls, such as
   GitHub Pages or a storage bucket behind HTTPS, and add its address to the
   App's redirect URIs.

   ```html theme={null}
   <!doctype html>
   <meta charset="utf-8">
   <meta name="referrer" content="no-referrer">
   <title>Workchats install code</title>
   <pre id="out"></pre>
   <script>
     const params = new URLSearchParams(location.search);
     document.getElementById("out").textContent = params.get("error")
       ? "Not installed: " + params.get("error")
       : "code:  " + params.get("code") + "\nstate: " + params.get("state");
   </script>
   ```

   Keep analytics and other third-party scripts off this page. The code is
   in its address.

2. Open the authorize URL above in a browser, signed in as a company admin,
   with this page as `redirect_uri`. Approve.

3. The page shows `code` and `state`. Check `state` is the value you sent.

4. Within 10 minutes, exchange the code:

   ```bash theme={null}
   curl -X POST https://public-api.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"'"
     }'
   ```

   On a shared machine, `-u` shows the secret to anyone who lists processes
   while curl runs. Put `user = "CLIENT_ID:CLIENT_SECRET"` in a file only you can
   read and pass it with `-K <file>` instead.

   `REDIRECT_URI` is the static page's address, exactly as registered. The
   answer carries `access_token`. Store it where your script reads its
   configuration. It doesn't expire.

5. Check it:

   ```bash theme={null}
   curl https://public-api.workchats.com/v1/auth/test \
     -H "Authorization: Bearer $ACCESS_TOKEN"
   ```

   `app_id` in the answer is your `client_id`.

A code is single-use and works only with your client secret, so a code
left in the browser history or the page host's logs is useless to anyone
else. Installing again later issues a new token, and the old one stops
working.

## Rotate a secret

On the App's page, rotate either secret. The new one is shown once, like
at registration.

| Secret | What happens to the old one |
| - | - |
| Client secret | Stops working at once. Tokens already issued keep working. Your App needs the new secret for its next code exchange |
| Signing secret | Keeps signing callbacks for 24 hours. During that time each callback carries two signatures, one from each secret, so update your App within the 24 hours. Rotating again within the 24 hours ends the oldest secret at once, and the secret you just replaced gets a fresh 24 hours |

See [Callbacks & signature verification](/guides/callbacks-and-signature-verification)
for checking two signatures.

## Disable, enable and delete

| Action | What happens |
| - | - |
| Disable | Uninstalls the App from your company if it is installed or disconnected. Its tokens stop working (`401 token_revoked`), the bot leaves every group and channel, and your callback URL gets `app.uninstalled` with `reason: "app_disabled"` and `removed_by: null`. No one can install a disabled App |
| Enable | Lets the App be installed again. It reinstalls nothing: install it again through the consent screen |
| Delete | Removes the App for good and frees its place in your 10. Refused while the App is installed in your company, or disconnected because your App revoked its own token. Disable it first, then delete it |

## Limits

Your company can have at most 10 Apps. Disabled Apps count. A deleted App
doesn't.

The **Apps** screen shows whether your company can register another App.
It reads one of four states, checked in this order:

| State | What it means | What the admin sees |
| - | - | - |
| `not_launched` | Company Apps aren't turned on in this environment yet | "Apps are coming soon." **Register an app** is hidden |
| `api_access_off` | API access is off for your company | A notice to contact Octogle to turn on API access. **Register an app** is disabled |
| `limit_reached` | Your company has 10 Apps | A notice to delete one to register another. **Register an app** is disabled |
| `available` | None of the above | **Register an app** is enabled |

In every state you can still edit, rotate, disable, enable and delete the
Apps your company already has.

Every registration, edit, rotation, disable, enable and delete is recorded
in your company's audit log, with the admin who did it. No entry contains a
secret.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.