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

# Authentication

> Authenticate with the DocuTrust API using API tokens, session cookies, or JWT bearer tokens.

DocuTrust supports three authentication methods. Choose the one that fits your integration pattern.

| Method             | Header                             | Best for                                           |
| ------------------ | ---------------------------------- | -------------------------------------------------- |
| **API Token**      | `X-Auth-Token: TOKEN`              | Server-side integrations, scripts, CI/CD pipelines |
| **Session Cookie** | `Cookie: _docutrust_session=VALUE` | Browser-based access after web login               |
| **JWT Bearer**     | `Authorization: Bearer TOKEN`      | Embedded signing forms and template builders       |

## API Token (recommended)

API tokens are the primary way to authenticate server-side API calls. Each token is scoped, rotatable, and tied to a specific user account.

### Creating a token

Navigate to **Settings > API** in the DocuTrust dashboard and click **Create Token**. You can also create tokens programmatically.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "$DOCUTRUST_URL/api/access_tokens" \
    -H "X-Auth-Token: $DOCUTRUST_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Production Backend",
      "scopes": ["templates:read", "templates:write", "submissions:read", "submissions:write"],
      "allowed_ips": ["203.0.113.0/24"],
      "expires_at": "2027-04-08T00:00:00.000Z"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(`${process.env.DOCUTRUST_URL}/api/access_tokens`, {
    method: "POST",
    headers: {
      "X-Auth-Token": process.env.DOCUTRUST_TOKEN,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "Production Backend",
      scopes: ["templates:read", "templates:write", "submissions:read", "submissions:write"],
      allowed_ips: ["203.0.113.0/24"],
      expires_at: "2027-04-08T00:00:00.000Z",
    }),
  });

  const token = await response.json();
  console.log("Token:", token.token); // Only shown once
  ```

  ```python Python theme={null}
  import requests
  import os

  url = f"{os.environ['DOCUTRUST_URL']}/api/access_tokens"
  headers = {
      "X-Auth-Token": os.environ["DOCUTRUST_TOKEN"],
      "Content-Type": "application/json",
  }
  payload = {
      "name": "Production Backend",
      "scopes": ["templates:read", "templates:write", "submissions:read", "submissions:write"],
      "allowed_ips": ["203.0.113.0/24"],
      "expires_at": "2027-04-08T00:00:00.000Z",
  }

  response = requests.post(url, headers=headers, json=payload)
  token = response.json()
  print("Token:", token["token"])  # Only shown once
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "id": 15,
  "name": "Production Backend",
  "user_id": 3,
  "token": "dt_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
  "token_prefix": "dt_live_a1b2c3d4",
  "scopes": ["templates:read", "templates:write", "submissions:read", "submissions:write"],
  "allowed_ips": ["203.0.113.0/24"],
  "expired": false,
  "expires_at": "2027-04-08T00:00:00.000Z",
  "last_used_at": null,
  "created_at": "2026-04-08T10:00:00.000Z"
}
```

<Warning>
  The full `token` value is only returned once at creation time. Store it securely -- you cannot retrieve it again. The `token_prefix` is always visible for identification purposes.
</Warning>

### Using the token

Pass the token in the `X-Auth-Token` header with every request:

```bash theme={null}
curl https://spitshake.io/api/templates \
  -H "X-Auth-Token: dt_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0"
```

### Available scopes

Scopes control which API operations a token can perform. Assign the minimum set of scopes your integration needs.

| Scope                | Allows                                                                        |
| -------------------- | ----------------------------------------------------------------------------- |
| `templates:read`     | List and retrieve templates, download template documents and thumbnails       |
| `templates:write`    | Create, update, clone, merge, and archive templates                           |
| `submissions:read`   | List and retrieve submissions, list and download documents                    |
| `submissions:write`  | Create submissions (single and bulk), archive submissions                     |
| `users:read`         | List and retrieve user accounts, view current user                            |
| `submissions:draft`  | Prepare draft submissions (create, update). No emails sent.                   |
| `submissions:send`   | Send submissions for signing (legally binding). Dispatches invitation emails. |
| `submissions:remind` | Send signing reminders to pending submitters.                                 |
| `submitters:write`   | Update submitter information and resend invitations.                          |
| `audit_events:read`  | Read the account-wide audit trail.                                            |

<Note>
  A token without any scopes is rejected on all endpoints. Always assign at least one scope.
</Note>

### IP allowlisting

Restrict a token to a set of IP addresses or CIDR ranges. Requests from any other IP will receive a `403 Forbidden` response. Leave `allowed_ips` empty (or omit it) to allow requests from any IP.

```json theme={null}
{
  "allowed_ips": ["203.0.113.10", "198.51.100.0/24", "2001:db8::/32"]
}
```

### Revoking a token

Revoke a token immediately when it is compromised or no longer needed:

```bash theme={null}
curl -X POST "$DOCUTRUST_URL/api/access_tokens/15/revoke" \
  -H "X-Auth-Token: $DOCUTRUST_TOKEN"
```

Revoked tokens are rejected immediately on all subsequent requests. This action cannot be undone.

### Listing tokens

Retrieve all tokens for your account. The full token value is never returned -- only the `token_prefix`.

```bash theme={null}
curl "$DOCUTRUST_URL/api/access_tokens" \
  -H "X-Auth-Token: $DOCUTRUST_TOKEN"
```

**Response:**

```json theme={null}
{
  "data": [
    {
      "id": 15,
      "name": "Production Backend",
      "user_id": 3,
      "token_prefix": "dt_live_a1b2c3d4",
      "scopes": ["templates:read", "templates:write", "submissions:read", "submissions:write"],
      "allowed_ips": ["203.0.113.0/24"],
      "expired": false,
      "expires_at": "2027-04-08T00:00:00.000Z",
      "last_used_at": "2026-04-08T14:32:00.000Z",
      "created_at": "2026-04-08T10:00:00.000Z"
    },
    {
      "id": 8,
      "name": "CI Pipeline",
      "user_id": 3,
      "token_prefix": "dt_live_x9y8z7w6",
      "scopes": ["submissions:read"],
      "allowed_ips": [],
      "expired": false,
      "expires_at": null,
      "last_used_at": "2026-04-07T09:15:00.000Z",
      "created_at": "2026-03-01T08:00:00.000Z"
    }
  ],
  "pagination": {
    "count": 2,
    "next": null,
    "prev": null
  }
}
```

## Session Cookie

When a user logs in through the DocuTrust web interface, the server sets a `_docutrust_session` cookie. This cookie authenticates all subsequent requests from the browser automatically.

Session cookies are primarily used by the DocuTrust frontend. They are useful if you are building a custom frontend that shares the same domain or if you need to make authenticated requests from browser JavaScript after a user has logged in.

### How it works

1. The user logs in at `https://spitshake.io/sign_in` with email and password (plus MFA if enabled).
2. The server returns a `Set-Cookie: _docutrust_session=VALUE` header.
3. The browser includes this cookie on all subsequent requests to the DocuTrust domain.

```javascript theme={null}
// Browser-side fetch (cookie is sent automatically)
const response = await fetch("https://spitshake.io/api/templates", {
  credentials: "include",
});

const data = await response.json();
```

### Session lifetime

* Sessions expire after **30 minutes** of inactivity.
* Security events (password change, MFA disable, privilege escalation) immediately invalidate all active sessions for the affected user.
* Logging out destroys the session server-side.

<Warning>
  Do not use session cookies for server-to-server integrations. They are tied to browser state and will expire. Use API tokens instead.
</Warning>

## JWT Bearer Token

JWT bearer tokens are designed for **embedded components** -- signing forms and template builders rendered inside your application using an iframe or the DocuTrust JavaScript SDK.

### Generating a token

Request a short-lived JWT from the embed token endpoint. This call must be made server-side using an API token.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "$DOCUTRUST_URL/api/embed/token" \
    -H "X-Auth-Token: $DOCUTRUST_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "resource_type": "submission",
      "resource_id": 187,
      "permissions": ["sign", "view"]
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(`${process.env.DOCUTRUST_URL}/api/embed/token`, {
    method: "POST",
    headers: {
      "X-Auth-Token": process.env.DOCUTRUST_TOKEN,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      resource_type: "submission",
      resource_id: 187,
      permissions: ["sign", "view"],
    }),
  });

  const { token, expires_at } = await response.json();
  // Pass this token to your frontend
  ```

  ```python Python theme={null}
  import requests
  import os

  url = f"{os.environ['DOCUTRUST_URL']}/api/embed/token"
  headers = {
      "X-Auth-Token": os.environ["DOCUTRUST_TOKEN"],
      "Content-Type": "application/json",
  }
  payload = {
      "resource_type": "submission",
      "resource_id": 187,
      "permissions": ["sign", "view"],
  }

  response = requests.post(url, headers=headers, json=payload)
  data = response.json()
  token = data["token"]
  ```
</CodeGroup>

**Response:**

```json theme={null}
{
  "token": "eyJhbGciOiJIUzI1NiJ9.eyJyZXNvdXJjZV90eXBlIjoic3VibWlzc2lvbiIsInJlc291cmNlX2lkIjoxODcsInBlcm1pc3Npb25zIjpbInNpZ24iLCJ2aWV3Il0sImV4cCI6MTcxMjU5NTIwMH0.abc123signature",
  "expires_at": "2026-04-08T14:00:00.000Z"
}
```

### Using the token

Pass the JWT in the `Authorization` header:

```bash theme={null}
curl "$DOCUTRUST_URL/api/embed/submission/187" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..."
```

Or pass it to the DocuTrust JavaScript SDK when initializing an embedded component:

```html theme={null}
<div id="docutrust-signing"></div>
<script src="https://spitshake.io/embed/sdk.js"></script>
<script>
  DocuTrust.embed({
    target: "#docutrust-signing",
    token: "eyJhbGciOiJIUzI1NiJ9...",
    type: "signing",
  });
</script>
```

### Token properties

| Property  | Value                                                     |
| --------- | --------------------------------------------------------- |
| Algorithm | HS256                                                     |
| Expiry    | 4 hours from creation                                     |
| Renewable | No -- generate a new token when the current one expires   |
| Scope     | Limited to the specific `resource_type` and `resource_id` |

<Note>
  JWT tokens can only access the specific resource they were created for. A token scoped to submission 187 cannot access submission 188.
</Note>

## OAuth 2.1 (Authorization Code + PKCE)

For connector directories and third-party AI agents. OAuth provides delegated access — users authorize your application without sharing their API token.

### Discovery

```bash theme={null}
GET /.well-known/oauth-authorization-server
```

Returns authorization, token, and revocation endpoints, supported scopes, and PKCE methods.

### Flow

1. **Register your client** via `POST /oauth/register` (Dynamic Client Registration) or the admin UI at `/oauth/applications`
2. **Redirect the user** to `/oauth/authorize` with `response_type=code`, `client_id`, `redirect_uri`, `scope`, `code_challenge` (S256), and `code_challenge_method`
3. **Exchange the authorization code** at `POST /oauth/token` with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, and `code_verifier`
4. **Use the access token** as `Authorization: Bearer <token>` on any API or MCP endpoint
5. **Refresh** with `grant_type=refresh_token` when the access token expires (1 hour)

PKCE is **mandatory** for all clients (OAuth 2.1 requirement). Consent is always required.

## Error responses

Authentication failures return standard HTTP error codes:

| Status                  | Meaning                                        | Common cause                                                              |
| ----------------------- | ---------------------------------------------- | ------------------------------------------------------------------------- |
| `401 Unauthorized`      | Missing or invalid credentials                 | No token provided, token revoked, token expired, invalid JWT              |
| `403 Forbidden`         | Valid credentials but insufficient permissions | Token scope does not include the required permission, IP not in allowlist |
| `429 Too Many Requests` | Rate limit exceeded                            | More than 120 requests per minute from the same token                     |

**Example 401 response:**

```json theme={null}
{
  "error": "unauthorized",
  "message": "Invalid or expired API token. Verify your X-Auth-Token header.",
  "status": 401
}
```

**Example 403 response:**

```json theme={null}
{
  "error": "forbidden",
  "message": "Token does not have the required scope: templates:write",
  "status": 403
}
```

## Security best practices

<AccordionGroup>
  <Accordion title="Use the principle of least privilege">
    Assign only the scopes your integration actually needs. A webhook consumer that only reads submission status should not have `templates:write`.
  </Accordion>

  <Accordion title="Rotate tokens regularly">
    Set an `expires_at` date when creating tokens. Create a new token before the old one expires, update your integration, then revoke the old token.
  </Accordion>

  <Accordion title="Enable IP allowlisting">
    Lock your production tokens to your server's IP range. This prevents stolen tokens from being used outside your infrastructure.
  </Accordion>

  <Accordion title="Never expose tokens client-side">
    API tokens must only be used in server-side code. For browser-based integrations, use JWT bearer tokens generated by your backend.
  </Accordion>

  <Accordion title="Monitor token usage">
    Review the `last_used_at` timestamp on your tokens. Revoke any token that has not been used in a long time or that you do not recognize.
  </Accordion>

  <Accordion title="Enable MFA on your account">
    If your account has MFA enforcement enabled, all users must verify a TOTP code at login. This protects against credential theft that could lead to token creation by an attacker.
  </Accordion>
</AccordionGroup>

## API Versioning

All API responses include an `X-API-Version: v1` header. The API is available at both `/api/*` and `/api/v1/*` (explicit version pin). Future breaking changes will be introduced under `/api/v2`.
