---
id: authentication
title: 'Authentication Guide'
description: 'Access tokens, Basic authentication and the headers every Digital.ai Agility API request needs.'
sidebar_label: 'Authentication'
sidebar_position: 2
tags: ['authentication', 'security', 'access-token']
---

Every Agility API endpoint requires authentication except `meta.v1`, which is
anonymous by design — the schema is not instance data. The method for API
clients is a **token** sent as a Bearer header: an Agility-issued personal
access token, or on [OIDC-configured instances](#oidc-access-tokens) a token
from the identity provider. Almost all customer instances authenticate
members through **SSO — the recommended configuration for security** — via
OIDC, or SAML where configured; API clients on those instances use tokens.
([Basic authentication](#basic-authentication) exists, but it is effectively
a dev/demo setting; some on-premise instances instead use
[Windows Integrated Authentication](#windows-integrated-authentication).)

## Access Token Authentication (Recommended)

### Step 1: Generate an Access Token

1. Log into your Agility instance
2. Open the member menu (your avatar) and choose **Applications**
3. Enter an application name (e.g., "My Integration") and select type
   **Personal**
4. Click **Add**
5. The access token is displayed **once** — copy it and store it securely.
   You cannot view it again after closing the popup.

Agility-issued tokens always begin with `1.` followed by 28 characters of
base64 — knowing the shape helps you spot a truncated paste. (Tokens issued
by an identity provider on [OIDC-configured instances](#oidc-access-tokens)
look different.)

:::note
An administrator can disable access tokens for an instance. If the
Applications page will not issue one, ask your administrator.
:::

### Step 2: Store Your Token Securely

**Environment variables** (recommended):

```bash
# Linux/macOS
export AGILITY_BASE_URL="https://www7.v1host.com/YourInstance"
export AGILITY_ACCESS_TOKEN="your-token-here"

# Windows PowerShell
$env:AGILITY_BASE_URL="https://www7.v1host.com/YourInstance"
$env:AGILITY_ACCESS_TOKEN="your-token-here"
```

Examples across this site assume those two variables. **Never** commit tokens
to source control, share them in chat or email, store them in plain text in a
codebase, or reuse one token across applications.

### Step 3: Include Authentication Headers

```http
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Accept: application/json
```

## Code Examples

### cURL

```bash
curl "${AGILITY_BASE_URL}/query.v1" \
  -H "Authorization: Bearer ${AGILITY_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "from": "Story",
    "select": ["Number", "Name"],
    "page": { "size": 50, "start": 0 }
  }'
```

### Python

```python
import os
import requests

class AgilityClient:
    def __init__(self):
        self.base_url = os.environ['AGILITY_BASE_URL']
        self.token = os.environ['AGILITY_ACCESS_TOKEN']
        self.headers = {
            'Authorization': f'Bearer {self.token}',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }

    def query(self, payload):
        response = requests.post(
            f'{self.base_url}/query.v1',
            headers=self.headers,
            json=payload,
            timeout=30
        )
        response.raise_for_status()
        return response.json()

# Usage
client = AgilityClient()
result = client.query({
    'from': 'Story',
    'select': ['Number', 'Name'],
    'page': {'size': 50, 'start': 0}
})
```

### TypeScript

```typescript
class AgilityClient {
  private baseUrl: string;
  private token: string;
  private headers: HeadersInit;

  constructor(baseUrl: string, token: string) {
    this.baseUrl = baseUrl;
    this.token = token;
    this.headers = {
      Authorization: `Bearer ${token}`,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    };
  }

  async query(payload: unknown): Promise<unknown> {
    const response = await fetch(`${this.baseUrl}/query.v1`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      throw new Error(`Query failed: ${response.status}`);
    }

    return response.json();
  }
}

// Usage
const client = new AgilityClient(process.env.AGILITY_BASE_URL!, process.env.AGILITY_ACCESS_TOKEN!);

const result = await client.query({
  from: 'Story',
  select: ['Number', 'Name'],
  page: { size: 50, start: 0 },
});
```

## Basic Authentication (Dev and Demo Instances Only) {#basic-authentication}

Basic authentication works only where Agility itself stores the member's
password — which in practice means development and demo instances. Almost
all customer instances authenticate through SSO, the recommended
configuration for security reasons, and there Basic does not apply. Treat
it as a local convenience, never an integration strategy:

```bash
# suitable for a local dev instance only
curl -u "username:password" "${AGILITY_BASE_URL}/rest-1.v1/Data/Story?sel=Name&page=1,0"
```

Even where it works, prefer tokens: a leaked password exposes the whole
account and every session, while a leaked token can be revoked on its own.

## Windows Integrated Authentication (On-Premise Only) {#windows-integrated-authentication}

On-premise installations on **Enterprise and Ultimate editions** can be
configured to authenticate members through **Windows Integrated Authentication
(NTLM)** instead of Agility's own login. An administrator selects it during
installation, when IIS has the Windows Authentication component.

On these instances IIS turns off anonymous access for `rest-1.v1`, so a Bearer
token sent there gets an NTLM challenge and never reaches Agility. Send
access-token requests for `rest-1.v1` to `~/rest-1.oauth.v1` instead (the SDKs
do this with `UseOAuthEndpoints`). `query.v1` and `api/asset` accept the token
at their usual paths. [Basic authentication](#basic-authentication) isn't
available on these instances, so use
[Access Token Authentication](#access-token-authentication-recommended) for API
clients.

## OIDC Access Tokens

Instances configured for OIDC single sign-on also accept access tokens
issued by **the identity provider Agility is directly configured against**
as `Bearer` credentials — an alternative to Agility-generated tokens in
those environments. In most OIDC configurations the authentication is
**federated**: the Digital.ai platform sits between Agility and your
organization's own identity provider. In that arrangement, the tokens that
work are the ones issued by the **Digital.ai platform** — tokens issued
directly by your organization's identity provider are *not* accepted.
Whether this applies to your instance, and how to obtain a working token,
depends on the identity configuration; ask your administrator.

## Token Lifecycle

**Tokens do not expire.** A token stays valid until you revoke it or an
administrator disables access tokens for the instance — which makes deliberate
lifecycle habits more important, not less:

- Name tokens for what uses them ("CI/CD Pipeline", "Local Development") so
  revocation is surgical
- Create separate tokens per application
- Rotate on a schedule of your choosing, and immediately when a team member
  leaves or a token may have been exposed: create the new token, switch your
  secrets over, verify, then revoke the old one

**A token is not a scoped credential.** It always acts with the full rights
and privileges of the member it belongs to — Agility currently offers no way
to narrow an access token's reach. To limit what an integration can
do, run it as a dedicated member whose role grants only what it needs — see
[Roles and permissions](/reference/roles-and-permissions).

**To revoke a token**: member menu → **Applications** → remove the
application entry. Revocation is immediate.

## A Note on OAuth 2.0

The Agility API has **never supported OAuth 2.0** — despite naming, in old
endpoint paths and internal machinery, that suggests otherwise.

You may still see paths like `rest-1.oauth.v1` in older material. They were
never OAuth 2.0: they existed so that access tokens could authenticate on
instances using delegated SSO. Use the standard endpoints with a Bearer
token instead, except on instances using
[Windows Integrated Authentication](#windows-integrated-authentication).

(The broader [Digital.ai Platform API](https://docs.digital.ai/platform/docs/api/api-overview)
is a separate product surface with its own authentication.)

## Testing Authentication

```bash
# meta.v1 needs no credentials — a 200 here proves the URL is right
curl "${AGILITY_BASE_URL}/meta.v1/Story?accept=application/json" \
  -H "Accept: application/json"

# a 200 here proves the token works
curl "${AGILITY_BASE_URL}/query.v1" \
  -H "Authorization: Bearer ${AGILITY_ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"from":"Story","select":["Number"],"page":{"size":1,"start":0}}'
```

Testing the two in that order separates "wrong URL" from "bad credentials" —
the two most common setup failures.

## Common Authentication Errors

| Error              | Likely cause                                                | What to check                                                        |
| ------------------ | ----------------------------------------------------------- | -------------------------------------------------------------------- |
| `401 Unauthorized` | Missing or invalid token; access tokens disabled instance-wide | The `Authorization` header, the token value, and — if both look right — whether your administrator has disabled tokens |
| `403 Forbidden`    | Authenticated, but your member's role lacks the right       | The member's role and project access                                  |

## Security Best Practices

### ✅ Do

- Store tokens in environment variables or a secret manager
- Use separate tokens for dev, staging and production
- Rotate tokens on a schedule — nothing expires them for you
- Revoke immediately on any suspicion of exposure
- Run integrations as a dedicated member with a minimal role
- Use HTTPS for every request

### ❌ Don't

- Commit tokens to source control, or share them in chat or email
- Reuse one token across applications
- Put credentials in URLs or query strings
- Assume a token is limited — it carries the member's full rights and privileges

## Troubleshooting

### "401 with a Correct-Looking Token"

Check the header shape — the space between `Bearer` and the token matters,
and the token itself must start with `1.`:

```text
Authorization: Bearer 1.abc123...
```

An Agility-issued token always starts with `1.` — anything else is most
often a truncated or mangled paste (or, on OIDC-configured instances, an
identity-provider token, which is legitimate).

### "Works in Browser but Not in Code"

The browser is riding a cookie-based session from the UI login. Code must
send explicit credentials on every request.

## Related Topics

- [Quickstart](/getting-started/quickstart) — first API call
- [Roles and permissions](/reference/roles-and-permissions) — what a member's role allows
- [Error handling](/guides/error-handling) — handling auth errors in code