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

# LLM Context Reference

> Machine-readable SDK reference. Paste as context into an AI assistant to generate accurate Aptly SDK code.

## Identity

* Script tag: `<script src="https://preview.getaptly.com/api/ai/app-builder/sdk.js"></script>`
* Global: `window.aptly` — auto-init on script load
* API base: `https://core-api.getaptly.com`

***

## State (read-only after init)

```
aptly.token           string|null         current delegate token
aptly.org             {id, name}          logged-in user's org
aptly.user            {id, email,         logged-in user
                       firstName, lastName,
                       phone, role, teams}
aptly.config          object              merged admin config — read any key directly
aptly.board           {id, schema}|null   board context; null unless board-tab embed
aptly.boards          object|null         prefetched standard boards (marketplace apps)
aptly.actions         string[]            action IDs admin enabled for this app (may be empty)
```

***

## Methods

### `aptly.requestToken(opts?)`

```
opts undefined  → Promise<string|null>                        v1, backward compat
opts {version:2} → Promise<{token:string|null, error:TokenError|null}>  v2, preferred

TokenError: { code: string, message: string, retryAfterMs: number|null }
Error codes: 'RATE_LIMITED' | 'TOKEN_ERROR'
```

### `aptly.fetch(path, options?)`

```
→ Promise<Response>
Auth-aware. Prepends API base URL. Auto-retries once on 401.
path example: '/board/UUID?page=0'
Do NOT set Authorization header manually.
```

### `aptly.startEmulation(token, opts?)`

```
→ Promise<window.aptly>
Dev/test only. token = long-lived dev token from Settings → Profile → Developer Tools.
opts: { config: {}, org: {}, user: {}, boards: {} }
Ignored when running inside Aptly iframe.
```

### `aptly.action(actionId, payload?)`

```
→ Promise<{ success: boolean, error?: string }>
Raw action dispatcher. actionId must be in aptly.actions or returns { success: false, error: 'Not permitted' }.
Timeout after 3s → { success: false, error: 'timeout' }.
```

### Named action wrappers (generated from aptly.actions)

```
aptly.openEmailComposer({ to?, subject?, content?, composeMode? })
aptly.createCard({ boardId, fields? })
aptly.createTask({ title?, dueAt?, priority?, assigneeId?, aptletInstanceId? })
aptly.createContact({ firstname?, lastname?, phone?, email? })
aptly.navigateToContact({ contactId })
aptly.openCardPane({ cardId })
aptly.openCardView({ cardId })
aptly.startDialer({ number, name? })
aptly.createEvent({ date?, comments?, recipients? })

All return Promise<{ success: boolean, error?: string }>.
Only callable if the action ID is in aptly.actions (admin-configured allowlist).
```

***

## Canonical pattern (always use this)

```html theme={null}
<head>
  <script src="https://preview.getaptly.com/api/ai/app-builder/sdk.js"></script>
</head>
<body>
  <script>
    async function init() {
      const { token, error } = await aptly.requestToken({ version: 2 });

      if (error?.code === 'RATE_LIMITED') {
        setTimeout(init, error.retryAfterMs);
        return;
      }
      if (!token) return;

      const boardId = aptly.config.BOARD_ID;
      const res = await aptly.fetch('/board/' + boardId + '?page=0');
      const { records } = await res.json();
      // render records
    }

    init();
  </script>
</body>
```

***

## Dev testing (pick one, remove before deploy)

**Option A — `startEmulation` (recommended):** real identity, requires dev token from Settings → Profile → Developer Tools

```html theme={null}
<script src="https://preview.getaptly.com/api/ai/app-builder/sdk.js"></script>
<script>
  aptly.startEmulation('PASTE_DEV_TOKEN_HERE', {
    config: { BOARD_ID: 'lease' }
  }).then(init);
</script>
```

**Option B — `window.APTLY_DEV` (mock, no network):** set BEFORE script tag

```html theme={null}
<script>
  window.APTLY_DEV = {
    token: 'your-api-key',
    config: { BOARD_ID: 'lease' },
    org:   { id: 'company-id', name: 'Acme' },
    user:  { email: 'dev@co.com', firstName: 'Dev' }
  };
</script>
<script src="https://preview.getaptly.com/api/ai/app-builder/sdk.js"></script>
```

**Option C — URL params:** no code change

```
?aptly_token=KEY&aptly_config_BOARD_ID=lease&aptly_org_name=Acme
```

***

## Rate limits

```
60 token requests / 60s / browser connection (not per user)
Error code: 'RATE_LIMITED'
retryAfterMs: ms until bucket resets
```

***

## Config scoping

```
contextScope 'company'  one config per org                       default
contextScope 'board'    one config per board
contextScope 'user'     one config per user
contextScope 'all'      all three; user > board > company merge
```

App code always reads `aptly.config.KEY` — Aptly merges before delivery.

***

## Scopes

```
Read scopes:  boards:*, contacts:*, knowledge:*
Write scopes: boards:*
Declared on app definition. Enforced by core-api.
403 = valid token, missing scope.
```

***

## Error reference

```
400  —                 missing required field
400  INVALID_DATA      bad scope format
401  INVALID_ACCESS_TOKEN  expired / wrong company
401  UNAUTHORIZED      user/app not found or revoked
403  FORBIDDEN         valid token, missing scope
429  RATE_LIMITED      too many token requests; retryAfterMs in error object
```

***

## Rules for LLM code generation

1. Always use `requestToken({ version: 2 })` — not `requestToken()`.
2. Always handle `error?.code === 'RATE_LIMITED'` with `setTimeout(init, error.retryAfterMs)`.
3. Never set `Authorization` header manually — `aptly.fetch` does it.
4. `aptly.config` is flat — read keys directly, no scope logic needed.
5. `aptly.board` is null unless the app is embedded as a board tab.
6. `startEmulation` + dev block must be removed before deploy.
7. SDK script tag goes in `<head>`, not `<body>`.
