---
title: "Notion Workers: Deploy Your First TypeScript Worker"
description: "Deploy your first Notion Worker in TypeScript: install the ntn CLI, scaffold the project, clear the type check, then prove the deploy with run logs."
canonical: "https://adamtheautomator.com/notion-workers-deploy-typescript/"
---

# Notion Workers: Deploy Your First TypeScript Worker

> Deploy your first Notion Worker in TypeScript: install the ntn CLI, scaffold the project, clear the type check, then prove the deploy with run logs.

Source: https://adamtheautomator.com/notion-workers-deploy-typescript/

---

ATA Learning

Tap to hide

[

ATA Learning

](/)

*   [Home](/)
*   [Tutorials](/tutorials/)
*   [Instructors](/author/)
*   [Advertising](/advertising/)
*   [Recommended Resources](/resources/)
*   [About Adam](/about-adam/)

Search for:  

*   [](https://twitter.com/adbertram)
*   [](https://github.com/Adam-the-Automator)
*   [](https://www.linkedin.com/company/adam-the-automator-llc)
*   [](/feed/)

![Notion Workers: Deploy Your First TypeScript Worker](https://adamtheautomator.com/wp-content/uploads/publisher/3f25d9c85b2b81e6ade6f5c2940472f2/1b288668ea56ed053535a8308a7525d38902b2e42410e4de992be94d21ace861.jpg)

# Notion Workers: Deploy Your First TypeScript Worker

[![](https://secure.gravatar.com/avatar/d0b9d42e21e5622713f8b693aa5c0f9244d5f7dd200ed29b8398f52dee5de337?s=192&d=mm&r=g)Adam Bertram](https://adamtheautomator.com/author/adam-bertram/)12 November 202612 min. read

Categories: [DevOps](/category/devops/)

Tags:[Notion](/tag/notion/)[TypeScript](/tag/typescript/)[NodeJS](/tag/nodejs/)[Serverless](/tag/serverless/)

Table of Contents

*   [Where Notion Workers Actually Run](#where-notion-workers-actually-run)
*   [What the Comparison Changes in Practice](#what-the-comparison-changes-in-practice)
*   [A Capability Is a Named Entry Point](#a-capability-is-a-named-entry-point)
*   [Prerequisites for the First Worker](#prerequisites-for-the-first-worker)
*   [Why the Node Floor Is Higher Than the Docs Advertise](#why-the-node-floor-is-higher-than-the-docs-advertise)
*   [Install the Notion CLI on macOS, Windows, or Through npm](#install-the-notion-cli-on-macos-windows-or-through-npm)
*   [Confirm the Install and Sign In](#confirm-the-install-and-sign-in)
*   [Scaffold the Project and Read What the Starter Contains](#scaffold-the-project-and-read-what-the-starter-contains)
*   [What the Starter File Declares](#what-the-starter-file-declares)
*   [Deploy: Type Check First, Then Upload](#deploy-type-check-first-then-upload)
*   [Local Exec Runs the TypeScript Without a Deploy](#local-exec-runs-the-typescript-without-a-deploy)
*   [Deploy the Worker and Sign In](#deploy-the-worker-and-sign-in)
*   [Verify the Deploy: Hosted Exec and Run Logs](#verify-the-deploy-hosted-exec-and-run-logs)
*   [Hosted Exec Proves the Deployed Copy Answers](#hosted-exec-proves-the-deployed-copy-answers)
*   [Run Logs Prove What Happened Inside Notion](#run-logs-prove-what-happened-inside-notion)
*   [Revise the Tool and Prove the Change Landed](#revise-the-tool-and-prove-the-change-landed)
*   [The Schema Builder Has No Optional Fields](#the-schema-builder-has-no-optional-fields)
*   [When a Deploy Fails: Symptom, Cause, Fix](#when-a-deploy-fails-symptom-cause-fix)
*   [What a Worker Costs on a Real Schedule](#what-a-worker-costs-on-a-real-schedule)
*   [What You Have Running Now](#what-you-have-running-now)

Can the Notion workspace you are about to deploy into run custom code at all? Notion Workers are gated by plan, switched on per workspace, and an owner decides whether anyone besides themselves may create one. When that switch is off, `ntn workers deploy` fails with a message that says nothing about the setting behind it.

This tutorial takes you from an empty terminal to a Worker that answers a command and writes a log line you can read back.

## Where Notion Workers Actually Run

Type “notion workers github” into a search engine and Cloudflare’s documentation comes back first, which is fair: two platforms share a name and almost nothing else. Cloudflare Workers answer HTTP requests from a global network of edge locations. Notion Workers answer events inside one workspace: a schedule you declare, [a Custom Agent](https://adamtheautomator.com/notion-custom-agents-support-routing/) (a Notion agent you configure to call your tools) calling a tool, or an external service posting to a webhook URL Notion generates. Cloudflare’s Workers runtime is built on [V8 isolates](https://developers.cloudflare.com/workers/reference/how-workers-works/), which that page calls “lightweight contexts” starting around a hundred times faster than a Node process on a container, while [Notion’s overview](https://developers.notion.com/workers/get-started/overview) says code runs “in a sandboxed Node.js environment.”

| Dimension | Notion Workers | Cloudflare Workers |
| --- | --- | --- |
| Runtime | Sandboxed Node.js hosted by Notion | V8 isolates |
| Trigger | Schedule, agent tool call, webhook | HTTP requests, Cron Triggers, Queues |
| Public URL | None you define; webhook URLs are generated | Endpoints on your domains |
| Gate | Business or Enterprise plus owner enablement | Any Cloudflare account |

### What the Comparison Changes in Practice

Your storage is Notion itself, so a Worker that remembers something writes to a database you declared in code. Your Notion client arrives pre-authenticated through `context.notion`, which is why this first deploy has no token wiring.

## A Capability Is a Named Entry Point

The word Notion uses for the things you register is capability, and the Workers overview defines the whole model in two sentences. A Worker is “The container for your code. One worker per project.” A capability is “Something the worker can do, i.e. a sync, tool, or webhook.”

| Capability | Started by | Who supplies the Notion token |
| --- | --- | --- |
| `tool` | A Custom Agent, or `ntn workers exec` | Platform for an agent call; you for exec |
| `sync` | The schedule declared in code | You |
| `webhook` | An inbound HTTP event | You |

The first argument to `worker.tool()` is the key every command uses. `ntn workers exec sayHello` finds the tool by name, and the [tool guide](https://developers.notion.com/workers/guides/tools) warns that keys must stay stable because agent configuration points at them. Rename one after an agent is wired up and the agent keeps calling a key that no longer exists.

Notion dispatches capabilities; you never write a route or cron. A sync’s schedule is a string in the declaration (`"5m"`, `"1h"`, `"1d"`), and a tool’s trigger is an agent deciding to call it.

## Prerequisites for the First Worker

If you want to follow along hands-on, you’ll need:

*   A Business or Enterprise workspace, including a trial. Workers appear in those columns of [Notion’s pricing table](https://www.notion.com/pricing) and nowhere else.
    
*   An owner who has left Worker creation enabled for you. [Run custom code with Workers](https://www.notion.com/help/run-custom-code-with-workers) describes the three owner options: creation limited to owners, opened to named people or groups, or Workers off entirely.
    
*   Node 22 LTS and npm 10.9.9 or newer. The template’s engines field is stricter than the quickstart’s floor, and npm warns rather than fails.
    
*   A git repository for the source, since Notion does not hand your code back.
    
*   Developer Mode if you want to read run logs inside Notion rather than the terminal, under Settings → Developer Mode.
    

### Why the Node Floor Is Higher Than the Docs Advertise

The quickstart asks for Node 22 and npm 10. The template that `ntn workers new` generates declares `"node": ">=22.23.4"` and `"npm": ">=10.9.9"` in its engines block, while the SDK package declares `>=22.0.0`. Satisfy one reading and miss the other, and npm says so without stopping:

```text
npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE   package: '@notion-cookbook/workers-default@0.0.0',
npm warn EBADENGINE   required: { node: '>=22.23.4', npm: '>=10.9.9' },
npm warn EBADENGINE   current: { node: 'v22.22.3', npm: '12.0.2' },
npm warn EBADENGINE }
```

That warning came from Node 22.22.3: past the 22 the quickstart asks for, short of the 22.23.4 the template requires. The install finishes anyway.

## Install the Notion CLI on macOS, Windows, or Through npm

The CLI is `ntn`, and it ships in public beta, so command names move between releases. On macOS and Linux the installer is a script:

```bash
curl -fsSL https://ntn.dev | bash
```

If `/usr/local/bin` isn’t writable, send the binary somewhere already on your `PATH`:

```bash
curl -fsSL https://ntn.dev | NTN_INSTALL_DIR="$HOME/.local/bin" bash
```

That shell installer doesn’t run on Windows at all, which is where a curl-only guide leaves Windows readers stuck. WinGet covers Windows x64:

```powershell
winget install Notion.ntn
```

Any platform with Node 22 and npm 10 can take the registry route, which is also the easiest to pin in CI:

```bash
npm install --global ntn
```

### Confirm the Install and Sign In

Open a new terminal so the binary lands on your `PATH`, then ask its version:

```bash
ntn --version
```

```text
ntn 0.23.29
```

The [installation page](https://developers.notion.com/cli/get-started/installation) documents the current routes. Version numbers matter on a beta CLI: a tutorial written against 0.22 won’t match `--help` on 0.23.

```bash
ntn login
```

`ntn login` opens a browser and stores credentials in the OS keychain. For a container or CI job, set `NOTION_API_TOKEN` instead and add `NOTION_KEYRING=0` when there is no keychain. Secrets for a deployed worker go up separately with [`ntn workers env set`](https://developers.notion.com/workers/guides/secrets), and the [OAuth guide](https://developers.notion.com/workers/guides/oauth) puts that step before any authorize call: deploy, set the keys, redeploy so the worker picks them up, then `ntn workers oauth start <capability-key>`, where the key is the OAuth capability’s name, as in `ntn workers oauth start githubAuth`. Updates follow the install channel: `ntn update` for the script, `npm update --global ntn` for npm, `winget upgrade Notion.ntn` for WinGet. When something feels broken before you have written any code, `ntn doctor` reports on auth, keychain, network, and config.

## Scaffold the Project and Read What the Starter Contains

The name prompt only appears when you run this in a terminal that can answer it:

```bash
ntn workers new my-worker
cd my-worker
```

```text
✔ Worker project created

Next steps:

  1. cd my-worker
  2. npm install
  3. ntn workers deploy
```

The scaffold writes thirteen entries. Five decide how the project behaves:

*   `src/index.ts`, the single file defining the Worker and its capabilities.
    
*   `package.json`, where `build` runs `tsc` and `check` runs `tsc --noEmit` before type-checking `.examples/` through `tsc -p tsconfig.examples.json`.
    
*   `tsconfig.json` and `tsconfig.examples.json`, which type-check `src/` and `.examples/` separately.
    
*   `.examples/`, holding runnable examples of each capability type in the [default template](https://github.com/makenotion/notion-cookbook/tree/main/workers/templates/workers-default).
    
*   `.agents/` and `AGENTS.md`, the instructions the template hands a coding agent.
    

Then install the dependencies and read the `src/index.ts` you are about to upload.

```bash
npm install
npm install --save-dev @notionhq/client@2.2.15
```

That second line covers a scaffold problem: `@notionhq/workers@0.9.6` resolves `@notionhq/client` to 5.31.1, where the `Client` export the SDK imports is gone, so a clean scaffold dies on the first `--local` run below with `SyntaxError: The requested module '@notionhq/client' does not provide an export named 'Client'`. The troubleshooting table at the end carries the same row.

Here is `src/index.ts` as the scaffold writes it:

```typescript
import { Worker } from "@notionhq/workers"
import { j } from "@notionhq/workers/schema-builder"

const worker = new Worker()
export default worker

// Example agent tool that returns a greeting
// Delete this when you're ready to start building your own tools.
worker.tool("sayHello", {
  title: "Say Hello",
  description: "Returns a friendly greeting for the given name.",
  schema: j.object({
    name: j.string().describe("The name to greet."),
  }),
  execute: ({ name }) => `Hello, ${name}!`,
})
```

### What the Starter File Declares

The default-exported `Worker` instance is the deployment unit, and the registrations that run at import time are the manifest. `title` and `description` are prompt surface rather than comments, because an agent picks tools by reading them; the tool guide warns against descriptions as broad as “Run support operations.” The `schema` block validates input and types `execute`, and each `.describe()` is what an agent reads when filling that field.

`dist/` and `workers.json` appear later. The second records the deployed worker this directory belongs to; commit it so a teammate’s terminal targets the same worker.

The install sequence, in one window:

![Installing the ntn CLI](https://adamtheautomator.com/wp-content/uploads/publisher/243f68b655b049917349faef7942aba0e31420f4027f3a5635b28f3a86378408.jpg)

## Deploy: Type Check First, Then Upload

Deploy bundles your code and, by default, builds it in Notion’s cloud. Every attempt spends from a workspace budget of 100 builds a day and 10 in any five-minute window, per the [Limits reference](https://developers.notion.com/workers/reference/limits). A type error caught locally costs nothing, so the cheap check comes first.

```bash
npm run check
```

On a clean project that command prints nothing and exits 0. Break it the way readers break it, by adding a field the schema never declared:

```typescript
  execute: ({ name, greeting }) => `${greeting ?? "Hello"}, ${name}!`,
```

```text
src/index.ts(15,21): error TS2339: Property 'greeting' does not exist on type '{ name: string; }'.
```

The compiler is describing the contract. `execute` receives exactly the shape `j.object()` declared, so a field missing from the schema cannot be read. Put the original line back before you deploy, since a build spent on an error the local check already caught is a build you don’t get back.

```typescript
  execute: ({ name }) => `Hello, ${name}!`,
```

The revision section declares the field properly and shows what changes.

### Local Exec Runs the TypeScript Without a Deploy

`--local` runs the capability through `tsx` straight from source, with no worker involved:

```bash
ntn workers exec sayHello --local -d '{"name": "World"}'
```

```text
"Hello, World!"
```

Nothing was uploaded, so a pass proves the handler and schema agree. It proves nothing about the deployed copy, which is why it runs before the build.

### Deploy the Worker and Sign In

```bash
ntn workers deploy
```

With no `workers.json` yet this is a first deploy, so the CLI is creating a worker and needs a name: `ntn workers deploy --name my-worker`. Every later run rejects that flag, because the worker exists and its identity comes from `workers.json`. The [deploy entry in the reference](https://developers.notion.com/cli/reference/commands) covers both halves of that rule.

Here is the deploy I ran before signing in, unedited:

```text
error: No workspace selected.
  hint: Run `ntn login` first, or set NOTION_WORKSPACE_ID.
```

The hint is the whole fix. Run `ntn login` interactively, or set `NOTION_WORKSPACE_ID` to the workspace ID the [CLI’s environment variables](https://developers.notion.com/cli/reference/commands) table defines, which skips the workspace prompt in CI. A deploy that clears that line ends with a new version running your capabilities and a `workers.json` naming it.

## Verify the Deploy: Hosted Exec and Run Logs

Uploading and executing are separate events, and only one proves your tool works.

### Hosted Exec Proves the Deployed Copy Answers

Drop the flag and the same payload goes to the worker named in `workers.json`:

```bash
ntn workers exec sayHello -d '{"name": "World"}'
```

The [quickstart](https://developers.notion.com/workers/get-started/quickstart) expects the same string, now produced inside Notion’s sandbox. Run `exec` outside the project and the CLI can’t resolve a worker; pass `--worker-id` or `--workers-config-file`.

### Run Logs Prove What Happened Inside Notion

Every `exec` creates a run. Find it, then print it:

```bash
ntn workers runs list
ntn workers runs logs <run-id>
```

`ntn workers runs` uses a per-run model with no live stream: you discover a run with `runs list`, then read it with `runs logs <run-id>`, where `<run-id>` is the UUID `runs list` printed. `--plain` prints tab-separated rows without headers, which suits a script. The same runs appear in Notion under Developer Mode → **Workers** → **Logs**, where a teammate without CLI access will look.

* * *

_**Quick Win: Run `ntn workers exec <key> --local` before every deploy. It costs nothing and spends no builds, so it catches a schema and handler mismatch before the upload reaches the daily allowance.**_

* * *

Which path each check takes:

![Local exec versus hosted run](https://adamtheautomator.com/wp-content/uploads/publisher/89f4aa7e9d003c03f7507ad51fa81f450566d918db2e8a4184e17a2381c88aa7.jpg)

A hosted exec that returns your key, plus a readable run, is what a finished deploy looks like.

## Revise the Tool and Prove the Change Landed

Change the tool the way you would change a real one: add a second input and a log line.

```typescript
import { Worker } from "@notionhq/workers"
import { j } from "@notionhq/workers/schema-builder"

const worker = new Worker()
export default worker

worker.tool("sayHello", {
  title: "Say Hello",
  description:
    "Greets one person by name. Use when a teammate asks for a welcome message or a standup greeting.",
  schema: j.object({
    name: j.string().describe("The name to greet."),
    greeting: j
      .string()
      .nullable()
      .describe("Word to open the greeting with. Pass null for Hello."),
  }),
  hints: { readOnlyHint: true },
  execute: ({ name, greeting }) => {
    console.log(`sayHello called for ${name}`)
    return `${greeting ?? "Hello"}, ${name}!`
  },
})
```

The description now states when to use the tool, because an agent picks from descriptions. `hints.readOnlyHint` marks a capability that changes nothing, which matters once an agent weighs whether to ask a human first. And `console.log` is the whole observability mechanism for a tool: that line becomes the record [`runs logs`](https://developers.notion.com/cli/reference/commands) returns.

### The Schema Builder Has No Optional Fields

`.nullable()` is the only optionality hook the builder has, and there’s no `.optional()`. `j.object()` puts every property into the schema’s `required` array, so a field you add is a field every caller must send. Ask for the tool and leave it out:

```bash
ntn workers exec sayHello --local -d '{"name": "Ada"}'
```

```text
InvalidToolInputError: [
  {
    "instancePath": "",
    "schemaPath": "#/required",
    "keyword": "required",
    "params": {
      "missingProperty": "greeting"
    },
    "message": "must have required property 'greeting'"
  }
]
--SNIP--
error: Local execution failed.
```

Pass `null` and the fallback engages, which is what the description promised:

```bash
ntn workers exec sayHello --local -d '{"name": "Ada", "greeting": null}'
```

```text
sayHello called for Ada
"Hello, Ada!"
```

Run `ntn workers deploy`, then `ntn workers exec sayHello -d '{"name": "Ada", "greeting": null}'`. `ntn workers runs logs <run-id>` shows `sayHello called for Ada` above the returned string. A missing log line means the deployed copy is still the old one; a present one means you are reading the new version.

## When a Deploy Fails: Symptom, Cause, Fix

Notion’s own troubleshooting starts with eligibility. The Help Center states that “Workers are available on Business and Enterprise plans” and that owners can keep creation to themselves, so a deploy that fails on a workspace you can read pages in is usually that gate.

| Symptom | Cause | Fix |
| --- | --- | --- |
| `error: No workspace selected.` | The CLI has no workspace context | `ntn login`, or set `NOTION_WORKSPACE_ID` |
| Deploy refused with an access message | Plan or owner restriction | An owner enables Workers for the workspace |
| `error TS2339` before upload | The handler reads a field the schema lacks | Declare it, then pass `null` when absent |
| `exec` cannot find the worker | Ran outside the project directory | `cd` into the project or pass `--worker-id` |
| Local exec reports `does not provide an export named 'Client'` | SDK and `@notionhq/client` resolved to incompatible majors | `npm install --save-dev @notionhq/client@2.2.15` |
| The tool runs and nothing changes in Notion | A personal token inherits its owner’s pages; an internal connection token reaches only pages added under Connections | Test the change by hand, then check Connections |

A token-scope mismatch wastes an afternoon. A personal access token acts as its creator and reaches whatever they can see; a bot token reaches only what someone connected, as the [API client guide](https://developers.notion.com/workers/guides/api-client) explains.

* * *

_**Warning: The build budget is shared by the whole workspace, so a teammate’s builds spend yours. Three teammates iterating in one afternoon will exhaust 10 builds in five minutes, and the error arrives looking like a build failure rather than a quota.**_

* * *

## What a Worker Costs on a Real Schedule

The billing unit is a run: [a scheduled sync execution](https://adamtheautomator.com/notion-workers-github-issue-sync/), an agent-triggered tool call, and a handled webhook event each count as one. Notion meters runs in credits at roughly $0.0023 each, about 4,348 runs per 1,000 credits ($10), per [Understand pricing for Workers](https://www.notion.com/help/understand-pricing-for-workers). An agent run that calls four tools generates four Worker runs.

| Workload | Runs per month | Approximate cost |
| --- | --- | --- |
| Sync every 30 minutes | 1,440 | $3.31 |
| Sync every 15 minutes | 2,880 | $6.62 |
| Sync hourly | 720 | $1.66 |

The 30-minute row matters most, since 30 minutes is also the default schedule for a sync that declares none. Forty-eight runs a day across a 30-day month is 1,440 runs, or about $3.31, and the other rows match Notion’s published benchmarks.

* * *

_**Reality Check: Notion’s own pages disagree about what you pay today. The Help Center says Workers are “free to try on Business and Enterprise plans (including Business trials)” during the beta, with credits required only “eventually.” The [pricing comparison table](https://www.notion.com/pricing) already lists Workers Beta as “Requires Notion credits.” Confirm your workspace under Settings → Access & billing → Notion credits before you budget either way.**_

* * *

Schedule frequency drives the cost, and the agent-call multiplier is the surprise. Both show up in the credits dashboard before the invoice does. Workers earn their cost when Notion is where the result lands; a job that never touches Notion runs cheaper as a plain scheduled script, and a public endpoint on your own domain belongs on Cloudflare.

## What You Have Running Now

The question at the top has an answer you can check: a Worker deploys once the plan and the owner switch agree, and the run log is what proves it executed.

The tool is deliberately small. Later posts in this series use the same mechanics for [a lookup tool wired to a Custom Agent](https://adamtheautomator.com/notion-worker-customer-lookup/) and a [sync that writes into a Notion database](https://developers.notion.com/workers/guides/syncs): scaffold, check, deploy, exec, then `runs logs` for the evidence. Keep `workers.json` in git and read run IDs rather than guessing which version is live.

Share this article

[Share on X](https://twitter.com/intent/tweet?url=https%3A%2F%2Fadamtheautomator.com%2Fnotion-workers-deploy-typescript%2F&text=Notion%20Workers%3A%20Deploy%20Your%20First%20TypeScript%20Worker)[Share on Facebook](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fadamtheautomator.com%2Fnotion-workers-deploy-typescript%2F)[Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fadamtheautomator.com%2Fnotion-workers-deploy-typescript%2F)

## Related Posts

![](https://adamtheautomator.com/wp-content/uploads/publisher/3f25d9c85b2b81d1ab64dfa279a5857d/85b63aa642603bdd12b4fca256ee8c837736b9fabea4ca8078d1ba31916f346f.webp)

### [Automate GitHub Issue Sync with Notion Workers](/notion-workers-github-issue-sync/)

Give a Notion Worker your GitHub issues endpoint and sync them into a Notion database with a stable key, API pagination, and replace-mode cleanup.

![](https://adamtheautomator.com/wp-content/uploads/publisher/3f25d9c85b2b81adaabefac0bca5a4ae/55e11157df7f9c791afbd26ff75cebe00d49fbfd6a51ec905abb1e4dab0659b1.webp)

### [Never Leave Notion Tasks Open After a GitHub Merge](/close-notion-tasks-on-pr-merge/)

Close Notion tasks when a GitHub pull request merges, verifying the HMAC signature, filtering unmerged closures, and deduping repeat events.

![](https://adamtheautomator.com/wp-content/uploads/publisher/3f25d9c85b2b811ca960ed14d86985b1/923eae6c1f8fdcc5d36226cfc9f1bbf87f972302e969a84b03b2bf71088b8ae9.webp)

### [Stop Runaway Notion Agent Costs with the Agent API](/notion-agent-api-cost-tracking/)

Use the Notion Agent API to read credits and runs per agent, compute cost per run, and gate runaway spending with status and credit-limit controls.

## Categories

*   [IT Ops](/category/it-ops/)
*   [Cloud](/category/cloud/)
*   [DevOps](/category/devops/)
*   [Home Ops](/category/home-ops/)
*   [Information Security](/category/infosec/)
*   [Software Development](/category/software-development/)

## Site

*   [Home](/)
*   [Tutorials](/tutorials/)
*   [Instructors](/author/)
*   [Advertising](/advertising/)
*   [Recommended Resources](/resources/)
*   [About Adam](/about-adam/)

Copyright 2026© ATA Learning | [Privacy Policy](/privacy/)
