Start here: what you are creating
You are not creating an account on a shared OneNote System service. You are making your own copy of the project in accounts you control:
| Part | What it does | Who controls it |
|---|---|---|
| GitHub repository | Holds the open-source code and sends updates to Vercel | You |
| Vercel project | Runs the website and private capture API on the internet | You |
| Neon database | Stores encrypted Microsoft credentials, your chosen section, and hashed API keys | You |
| Microsoft app registration | Lets your deployment ask Microsoft for limited, revocable OneNote access | You |
| Shortcut | Sends title and text from your device to your deployment | You |
Expect the first setup to take roughly 20–40 minutes because both Vercel and Microsoft require several confirmation screens. You do not need to write code, but you do need to copy values exactly.
Part 1: deploy your copy to Vercel
What Vercel is: Vercel runs the OneNote System website and API for you. When a Shortcut sends a note, Vercel receives the request and runs the code. Your personal computer does not need to stay on.
Create or sign in to GitHub
Use GitHub's sign-up page ↗. GitHub stores your copy of the source code. Keep the repository private if you ever add personal changes, and never commit secret values.
Open the deployment link
Deploy OneNote System on Vercel ↗
Vercel will ask you to sign in. Choosing Continue with GitHub is easiest because Vercel needs permission to create and deploy your copy.
Create the Git repository
On the “Create Git Repository” screen, keep the name
onenote-systemor choose another name. This new repository belongs to your GitHub account; it is not the original project.Create the first deployment
Choose Deploy. The first deployment may show setup errors because the database and private settings do not exist yet. That is expected. Wait until Vercel gives you a project URL ending in
.vercel.app, then copy that complete URL.
Vercel can automatically deploy later changes pushed to the connected GitHub repository. See Vercel's official Git deployment documentation ↗ for how that relationship works.
Part 2: add a Neon database
What Neon is: Neon is a company that runs PostgreSQL databases. PostgreSQL is the database software; Neon operates it, secures the database server, and gives your Vercel project a private connection address. For light personal use, its free tier is usually enough, subject to Neon's current limits.
Why this is required: Vercel functions do not keep permanent files between requests. The database remembers your encrypted Microsoft connection, default section, and API keys after each request ends.
Open Neon inside the Vercel Marketplace
Visit the official Neon Marketplace listing ↗ while signed in to Vercel.
Add the integration
Choose Add Integration. Select your Vercel account and the OneNote System project you created above. Continue to Neon and create an account if asked.
Create the database
Use a recognizable project name such as
onenote-system. Use the region closest to you. Keep the generated database and role defaults unless you know you need something different.Confirm the project connection
Neon/Vercel should add an environment variable named
DATABASE_URLto the OneNote System project. You do not need to open or copy its value.Redeploy
In Vercel, open your OneNote System project, then Deployments. Open the ⋯ menu beside the newest deployment and choose Redeploy. Existing deployments cannot see settings added afterward.
Never share DATABASE_URL. Despite its name, it contains the database hostname, username, and password. Neon's official manual Vercel connection guide ↗ is available if the Marketplace integration does not add it automatically.
Part 3: add the private application settings
An environment variable is a private setting Vercel gives to the running app without placing it in GitHub. Open your Vercel project, then Settings → Environment Variables.
| Name | Required? | What to enter |
|---|---|---|
APP_ENCRYPTION_KEY | Yes | A new random secret used to encrypt Microsoft credentials and sign login cookies. Generate it once and do not change it casually. |
APP_URL | Yes | Your complete production URL, such as https://onenote-system-abc.vercel.app, with no trailing slash. |
DATABASE_URL | Yes | Neon should add this automatically. Do not replace it with the words shown in the example. |
CRON_SECRET | No | A separate random secret. The current maintenance endpoint performs no synchronization work, so this can be omitted. |
Generate the encryption key
If you have a Mac, open Terminal, run openssl rand -base64 32, and copy the result. On Windows, use a trusted password manager's password generator to create at least 43 random letters, numbers, and symbols. Do not use a normal password, your name, or the example text.
# Required: generate a new random value for each installation
APP_ENCRYPTION_KEY=PASTE_A_RANDOM_32_BYTE_KEY
# Required: the public address of this installation, with no trailing slash
APP_URL=https://YOUR-PROJECT.vercel.app
# Required: added automatically by the Vercel + Neon integration
DATABASE_URL=postgresql://...
# Optional: protects the currently empty maintenance cron endpoint
CRON_SECRET=PASTE_ANOTHER_LONG_RANDOM_VALUEApply each variable to Production, Preview, and Development if Vercel asks. Save, then redeploy again. Vercel documents these settings in its environment variable guide ↗.
Part 4: create your Microsoft app registration
What you are doing: Microsoft will not let an unknown website read or write OneNote. An app registration gives your deployment an identity, a list of permitted actions, and a callback address. You can remove the registration later to revoke the connection.
Keep your Vercel project URL nearby. The callback address must match exactly. A missing letter, extra slash, or http instead of https will stop sign-in.
Open Microsoft Entra
Go to the Microsoft Entra admin center ↗ and sign in with the Microsoft account that owns the OneNote notebooks. Personal Microsoft accounts may be redirected through an account/tenant setup screen.
Start an app registration
Open Identity → Applications → App registrations, then choose New registration. Microsoft's official app registration guide ↗ includes screenshots and definitions.
Name it and choose account access
Name it
OneNote System. Choose Accounts in any organizational directory and personal Microsoft accounts if you want the deployment to accept either work/school or personal Microsoft accounts.Add the Web redirect URI
For platform, choose Web. Enter
https://YOUR-VERCEL-DOMAIN/api/auth/microsoft/callback. Replace onlyYOUR-VERCEL-DOMAIN. Example:https://my-queue.vercel.app/api/auth/microsoft/callback.Copy the Application (client) ID
After registration, the Overview screen shows an Application (client) ID. Copy the long identifier. Do not copy the Object ID or Directory ID.
Create a client secret
Open Certificates & secrets → Client secrets → New client secret. Name it
OneNote System, choose an expiration, and create it. Copy the secret's Value immediately. Do not copy the Secret ID. Microsoft will never display the Value again.Add Microsoft Graph permissions
Open API permissions → Add a permission → Microsoft Graph → Delegated permissions. Add
User.Read,Notes.ReadWrite, andTasks.ReadWrite.offline_accessis requested during Microsoft sign-in so the connection can refresh without asking you every hour.Tasks.ReadWriteis what powers Microsoft To Do. Leave it out if you only want OneNote pages — everything else still works. If you add it to an existing deployment, reconnect Microsoft afterwards: a permission added later does not upgrade a connection that was already approved.
Microsoft's Graph documentation confirms that creating OneNote pages uses delegated OneNote permissions and that page content is sent as HTML. See Create OneNote pages ↗.
Part 5: connect Microsoft and select a section
Open your deployment's setup page
Use
https://YOUR-VERCEL-DOMAIN/setup. Do this on your deployed copy, not this project's public information site.Save the Microsoft app values
In Setup Step 2, paste the Application (client) ID and the client secret Value. The server encrypts the secret before storing it.
Connect your account
Setup Step 3 sends you to Microsoft. Verify that the browser address is a Microsoft domain, read the requested access, and approve it.
Choose the destination
In Setup Step 4, load notebooks, choose a notebook, and select the section that should behave as your capture inbox.
Create a Shortcut API key
Setup Step 5 displays the key exactly once. Save it in your password manager. The database stores only a hash and cannot show the original later.
Part 6: install or build a Shortcut
The Shortcut needs two values: your deployment URL and the ons_ API key from Setup Step 5. It does not need your Microsoft client secret, Microsoft password, database URL, or encryption key.
Open the complete Shortcuts guide →
The Shortcuts page identifies which iCloud download links are still missing, explains every configuration answer, and includes a complete manual Quick Inbox build that works without a download.
Part 7: use OneNote from a terminal
The onenotesystem command is the official command-line client. It takes the same two values a Shortcut does — your deployment URL and an ons_ API key — and calls the same two endpoints, so pages created from a shell and pages created from a phone are identical.
npm install -g onenotesystem onenotesystem configure onenotesystem capture "Standup notes" --content "Shipped the CLI"It exists for the machines OneNote will not run on: Linux desktops, servers you only reach over SSH, and work computers where the OneNote app cannot be installed. It needs Node.js 18.17 or newer and has no other dependencies.
OneNote API reference
The API can create a page in the signed-in user's default section or append plain text to an existing page.
| Method | Path | Authentication | Success |
|---|---|---|---|
POST | /api/capture | Authorization: Bearer ons_… | 201 Created |
POST | /api/append | Authorization: Bearer ons_… | 200 OK |
Create a page: POST /api/capture
| JSON field | Required? | Limit and behavior |
|---|---|---|
title | No | Plain text, trimmed to 200 characters. Defaults to “Untitled capture.” |
content | No | Plain text, up to 100,000 characters. New lines become line breaks. |
url | No | Included as a source link only when it begins with http:// or https://. |
Example request
curl -X POST "https://YOUR-DOMAIN/api/capture" \
-H "Authorization: Bearer ons_YOUR_PRIVATE_KEY" \
-H "Content-Type: application/json" \
--data '{
"title": "Saved from OneNote System",
"content": "This becomes the body of a new OneNote page.",
"url": "https://example.com/source"
}'Example success response
{
"ok": true,
"page": {
"id": "…",
"title": "Saved from OneNote System",
"webUrl": "https://…"
}
}Append to a page: POST /api/append
| JSON field | Required? | Limit and behavior |
|---|---|---|
pageTitle | One target required | Exact title of a page in the default section, up to 200 characters. Duplicate titles return 409. |
pageId | One target required | Microsoft Graph page ID. Use this instead of pageTitle to identify a page exactly or target another section. |
content | Yes | Non-empty plain text, up to 100,000 characters. New lines become line breaks. |
url | No | Appended as a source link only when it begins with http:// or https://. |
Example append request
curl -X POST "https://YOUR-DOMAIN/api/append" \
-H "Authorization: Bearer ons_YOUR_PRIVATE_KEY" \
-H "Content-Type: application/json" \
--data '{
"pageTitle": "Quick Inbox",
"content": "This text is added to the existing page.",
"url": "https://example.com/source"
}'Example append response
{
"ok": true,
"page": {
"id": "…",
"title": "Quick Inbox",
"webUrl": "https://…"
}
}Error responses use { "error": "Explanation" }. A 400 means the JSON or fields are invalid; 401 means the API key is missing/incorrect; 404 means the requested page title was not found; 409 means a default section is missing or a title is ambiguous; 500 means the database or Microsoft request failed.
Microsoft To Do
Available when the connection has approved Tasks.ReadWrite. All four use the same ons_ API key as the OneNote endpoints.
| Request | Does |
|---|---|
GET /api/todo/lists | Your To Do lists, default first |
GET /api/todo | Open tasks. ?list=, ?all=true, ?top= |
POST /api/todo | Create a task from title, and optional note, list, dueDate, reminder, timeZone |
POST /api/todo/complete | Mark a task done by id |
Send timeZone as an IANA name such as America/Chicago. A bare dueDate of 2026-09-15 is anchored to midnight in that zone, so the task stays on the day you meant rather than shifting for anyone west of UTC.
Security and data storage
| Information | Where it is stored | Protection |
|---|---|---|
| Setup step number | Your browser's localStorage | Not sensitive; used only to reopen the last viewed step |
| Light/dark preference | Your browser's localStorage | Not sensitive; used to restore the selected theme before the page appears |
| Login session | HttpOnly cookie | Signed; JavaScript cannot read it; Secure in production |
| Microsoft client secret | Your database | AES-256-GCM encrypted using APP_ENCRYPTION_KEY |
| Microsoft access/refresh tokens | Your database | AES-256-GCM encrypted |
| Discord bot token | Your database | AES-256-GCM encrypted; never returned to the browser after saving |
| Discord public key and application IDs | Your database | Not secrets; used to verify and route signed interactions |
| Shortcut API keys | Your database | One-way SHA-256 hash; original shown once |
| Captured note text | Sent to Microsoft during the request | Not intentionally retained by the OneNote System database |
Keep APP_ENCRYPTION_KEY, DATABASE_URL, Microsoft client secrets, refresh tokens, API keys, and Discord bot tokens out of GitHub and screenshots. If an API key leaks, create a replacement. Key revocation UI is not yet included, so database/manual administration is currently required to remove an old key.
This software is provided under the MIT License without warranty. Review the source and provider terms before using it for sensitive or regulated information.
Self-host with Docker
This route is for people comfortable maintaining a Linux server. You need Docker, Docker Compose, a domain name, HTTPS, backups, and a plan for applying updates.
Clone the repository
git clone https://github.com/DudeThatsErin/onenote-system.git cd onenote-system cp .env.example .env # Edit .env before continuing. docker compose up -d --buildDo not run the final command until the environment file and database password are changed.
Set a strong PostgreSQL password
Edit
docker-compose.ymlor supply the documented Compose variable. The password in the example is not safe for a public server.Complete
.envSet
APP_URLto your HTTPS domain, generateAPP_ENCRYPTION_KEY, and setDATABASE_URLto the PostgreSQL container connection string.Start the containers
Run
docker compose up -d --build, inspectdocker compose logs -f app, and verifyhttps://YOUR-DOMAIN/api/health.Put it behind HTTPS
Use Nginx, Caddy, Traefik, or another reverse proxy. Microsoft OAuth requires an exact, stable HTTPS callback address in production.
Back up PostgreSQL
The database contains the encrypted connection and configuration. Losing the database means reconnecting accounts and generating new API keys.
Update an installation
Vercel
Your deployed GitHub repository is a separate copy. GitHub does not automatically pull changes from the original project. Use GitHub's “Sync fork” feature if your repository is a fork, review changes, then let Vercel deploy the updated branch. Back up the database before a release that changes its schema.
Self-hosted
git pull --ff-only
docker compose build --pull
docker compose up -d
docker compose logs --tail=100 appRead release notes first and preserve your .env and PostgreSQL volume.
Discord integration
Setup Step 6 connects a Discord application to the same Microsoft account and default OneNote section used by this installation. It registers one /onenote command with two actions:
/onenote createcreates a page from a title, optional text, and optional source URL./onenote appendadds text and an optional source URL to an exact page title in the default section.
Discord application setup
Create the application
Open the Discord Developer Portal ↗, create an application, and copy the Application ID and Public Key from General Information.
Create the bot token
Open Bot, reset the token, and save it in Setup Step 6. OneNote System encrypts it. The token is used only when registering or removing the slash command; interaction responses use Discord's signed webhook flow.
Save the interaction endpoint
Save the Discord fields in OneNote System first. Copy the exact URL ending in
/api/discord/interactionsinto General Information → Interactions Endpoint URL. Discord sends a signed PING and the endpoint returns PONG during validation.List who may use the command
Enter your own Discord user ID in Allowed Discord user IDs, and anyone else you trust, separated by commas. The command writes into your OneNote, so seeing it in a server is not permission to use it — only the accounts listed here can run it. Leaving the field blank allows nobody. To find your ID, turn on Developer Mode in Discord's Advanced settings, right-click your name, and choose Copy User ID.
Install and register the command
Use the installation link to authorize the
applications.commandsscope. Then choose Save and register /onenote. A test server ID creates a guild command that updates immediately; leaving it blank creates a global command.Test both actions
Run
/onenote create, then run/onenote appendwith that page's exact title. Discord shows a private result and links to the OneNote page when Microsoft supplies a web URL.
The endpoint validates the raw request body with Discord's Ed25519 signature headers, acknowledges commands within Discord's three-second window, completes Microsoft Graph work in the background, and edits the private response afterward. See Discord's official interaction security guide ↗ and application command reference ↗.
Every Discord account on your allowed list can write to your selected OneNote section. Keep that list as short as you actually need, and remove people when they no longer need access. If the bot token leaks, reset it in Discord and save the replacement in Setup Step 6.
Troubleshooting
Setup says DATABASE_URL is not configured
The Neon integration was not connected to this Vercel project, or the app was not redeployed after it was connected. Check Vercel → Project → Settings → Environment Variables for DATABASE_URL, then redeploy.
Microsoft says the redirect URI does not match
Compare the URI in Entra with APP_URL plus /api/auth/microsoft/callback. They must match exactly, including https, subdomain, path, and absence of a trailing slash.
To Do commands say access has not been approved
The connection was approved before Tasks.ReadWrite was added. Add that delegated permission in your Microsoft app registration, then reconnect Microsoft in setup. OneNote keeps working the whole time.
Microsoft sign-in works but notebooks do not load
Confirm that the app registration has delegated Notes.ReadWrite permission and that you approved it. Reconnect the Microsoft account after changing permissions.
APP_ENCRYPTION_KEY is not configured
Add a strong random value in Vercel Environment Variables and redeploy. Do not change an existing key after credentials are stored; old encrypted values cannot be decrypted with a new key.
The Shortcut receives Unauthorized
Confirm the header is exactly Authorization: Bearer ons_…. There must be one space after Bearer. The key must come from the same deployment receiving the request.
The Shortcut says no default section is selected
Open Setup Step 4 on the same deployment and choose a notebook and section while signed in with Microsoft.
A secret expired
Create a new Microsoft client secret Value in Entra, save it through Setup Step 2, then reconnect Microsoft. Microsoft recommends rotating credentials and client secrets have limited lifetimes.
Discord rejects the Interactions Endpoint URL
Save the Application ID and Public Key in Setup Step 6 before pasting the endpoint into Discord. Confirm the URL uses HTTPS and ends exactly in /api/discord/interactions. A wrong public key causes the required signature check to return 401.
The /onenote command does not appear
Use a test server ID for immediate testing, confirm the application was installed with the applications.commands scope, and choose Save and register /onenote again. Global command changes can take longer to propagate through Discord.
Where can I report a reproducible bug?
Remove every secret and personal note from screenshots/logs, then open a GitHub issue ↗ with the deployment type, route, status code, and exact non-secret error.