# How to Integrate Replit with Evernote

- Tool: Replit
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with Evernote, complete the OAuth 1.0a authorization flow to obtain an access token, store it in Replit Secrets (lock icon 🔒), and use the Evernote Thrift SDK or REST API to read and write notes, notebooks, and tags in ENML format from your Python or Node.js server.

## Why Integrate Evernote with Replit?

Evernote holds years of personal and professional knowledge for millions of users — meeting notes, research clippings, project documentation, and reference material. Connecting Replit to the Evernote API lets you build automations that push content into Evernote from other tools, sync notes with external systems, extract structured data from note collections, or build custom search interfaces on top of a user's Evernote library.

Evernote's API differs from most modern REST APIs in an important way: it uses Apache Thrift as its protocol, which means the Python and Node.js SDKs use generated Thrift client code rather than simple HTTP calls. The Evernote SDK handles the protocol details, but you need to understand the data model: Notes contain ENML (Evernote Markup Language) content — an XML-based format derived from XHTML. When creating or updating notes, your ENML must be well-formed XML that conforms to the Evernote DTD, otherwise the API will reject the note. The SDK provides utilities for constructing valid ENML.

Authentication uses OAuth 1.0a, which is older than OAuth 2.0 but still widely used in the Evernote ecosystem. The flow requires a three-step handshake: request token → user authorization → access token. Because this involves a browser redirect, you will implement a small OAuth callback endpoint in your Replit server during the setup phase, then store the resulting access token in Replit Secrets for future server-to-server calls. Evernote also provides a Sandbox environment (sandbox.evernote.com) for development and testing before using the production API.

## Before you start

- An Evernote account (personal or business)
- A registered Evernote API application from dev.evernote.com (both Sandbox and Production keys)
- A Replit account with a Node.js or Python Repl created
- Basic understanding of OAuth authentication flows and REST API concepts
- Familiarity with Express (Node.js) or Flask (Python)

## Step-by-step guide

### 1. Register your app in the Evernote Developer Portal

Go to dev.evernote.com and sign in with your Evernote account. Navigate to 'Get an API Key' and fill out the application registration form. You will need to provide your application name, description, and what permissions it needs (Basic — read notes, or Full Access — read and write notes, notebooks, and tags). For most automation use cases, request Full Access. After submitting, Evernote provides you with a Consumer Key and Consumer Secret for the Sandbox environment. The Sandbox environment (sandbox.evernote.com) is a separate Evernote service for development — you will need a separate Sandbox account at sandbox.evernote.com to test your integration. To access the Production Evernote API, you must request activation from Evernote after demonstrating your app works in Sandbox. Keep your Consumer Key and Consumer Secret safe — these are the app-level credentials, separate from the per-user OAuth access tokens. Store both in Replit Secrets now as EVERNOTE_CONSUMER_KEY and EVERNOTE_CONSUMER_SECRET, along with a flag EVERNOTE_SANDBOX set to 'true' during development.

```
# Python — install Evernote SDK
# Run in Replit Shell:
# pip install evernote3
# Or add to requirements.txt:
# evernote3>=1.28.0

# For Node.js, run in Replit Shell:
# npm install evernote

# Verify installed:
import pkg_resources
try:
    pkg_resources.get_distribution('evernote3')
    print('Evernote SDK installed successfully')
except pkg_resources.DistributionNotFound:
    print('Run: pip install evernote3')
```

**Expected result:** Your app is registered in the Evernote Developer portal and you have Consumer Key and Consumer Secret values. EVERNOTE_CONSUMER_KEY, EVERNOTE_CONSUMER_SECRET, and EVERNOTE_SANDBOX are added to Replit Secrets.

### 2. Complete the OAuth 1.0a authorization flow

Evernote's OAuth 1.0a flow requires three steps that cannot be skipped: first, your server requests a temporary token from Evernote using your Consumer Key and Secret; second, the user is redirected to Evernote's authorization page to grant access; third, Evernote redirects back to your callback URL with a verifier code, which your server exchanges for a permanent access token. To implement this in Replit, add two routes to your server: /oauth/start (which fetches the request token and redirects the user to Evernote) and /oauth/callback (which handles the redirect back and exchanges the verifier for an access token). When the access token is obtained, print it to the console so you can copy it into Replit Secrets. You will only need to run this OAuth flow once per Evernote user account — the resulting access token does not expire under normal circumstances. Set your OAuth callback URL to your Replit preview URL + /oauth/callback during development, and update it to the production deployment URL later.

```
# Python — Evernote OAuth 1.0a flow (oauth_setup.py)
from flask import Flask, request, redirect
import os
from evernote.api.client import EvernoteClient

app = Flask(__name__)

CONSUMER_KEY = os.environ['EVERNOTE_CONSUMER_KEY']
CONSUMER_SECRET = os.environ['EVERNOTE_CONSUMER_SECRET']
SANDBOX = os.environ.get('EVERNOTE_SANDBOX', 'true').lower() == 'true'

# Replace with your Replit preview or deployment URL
CALLBACK_URL = os.environ.get('REPLIT_URL', 'http://localhost:3000') + '/oauth/callback'

client = EvernoteClient(consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, sandbox=SANDBOX)

@app.route('/oauth/start')
def oauth_start():
    request_token = client.get_request_token(CALLBACK_URL)
    # Store oauth_token temporarily (in production use a session or DB)
    app.config['REQUEST_TOKEN'] = request_token
    authorize_url = client.get_authorize_url(request_token)
    return redirect(authorize_url)

@app.route('/oauth/callback')
def oauth_callback():
    oauth_token = request.args.get('oauth_token')
    oauth_verifier = request.args.get('oauth_verifier')
    request_token = app.config.get('REQUEST_TOKEN', {})
    access_token = client.get_access_token(
        request_token.get('oauth_token'),
        request_token.get('oauth_token_secret'),
        oauth_verifier
    )
    # COPY THIS TOKEN TO REPLIT SECRETS AS EVERNOTE_ACCESS_TOKEN
    print(f"ACCESS TOKEN: {access_token}")
    return f"Access token obtained. Copy it to Replit Secrets: {access_token}"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** After visiting /oauth/start and authorizing in Evernote, the callback page displays your access token. You copy it to Replit Secrets as EVERNOTE_ACCESS_TOKEN.

### 3. Store credentials in Replit Secrets

Open the Replit Secrets panel (lock icon 🔒 in the sidebar) and ensure you have the following secrets configured: EVERNOTE_CONSUMER_KEY (your app's consumer key from the developer portal), EVERNOTE_CONSUMER_SECRET (your app's consumer secret), EVERNOTE_ACCESS_TOKEN (the per-user access token obtained from the OAuth flow), and EVERNOTE_SANDBOX (set to 'true' for development, 'false' for production). The access token is the most sensitive value — anyone with it can read and modify the authorized user's Evernote notes. Replit's Secret Scanner automatically flags tokens that match known patterns in code commits, but the best protection is to never let the token appear in source files at all. After the OAuth setup phase, remove the OAuth routes from your app and replace them with API calls that use the stored access token directly. Install any remaining dependencies via the Replit Packages panel or Shell commands.

```
// Node.js — verify Evernote secrets (verify-secrets.js)
const required = ['EVERNOTE_CONSUMER_KEY', 'EVERNOTE_CONSUMER_SECRET', 'EVERNOTE_ACCESS_TOKEN'];
for (const key of required) {
  const val = process.env[key];
  if (!val) {
    console.error(`MISSING: ${key} — add it in Replit Secrets (lock icon 🔒)`);
    process.exit(1);
  }
  console.log(`OK: ${key} loaded (${val.length} chars)`);
}
console.log('All Evernote secrets verified.');
```

**Expected result:** All four Evernote secrets appear in the Replit Secrets panel. The verification script confirms they are all loaded and non-empty.

### 4. Read notebooks and create notes with ENML

With the access token stored, you can now make authenticated API calls. The Evernote SDK provides a NoteStore client that wraps all note and notebook operations. To list notebooks, call noteStore.listNotebooks(). To create a note, construct a Note object with a title, content (ENML string), and optional notebook GUID. The ENML format is critical: every note content must start with the ENML DOCTYPE declaration and use only tags allowed by the Evernote DTD. Plain text content must be wrapped in ENML paragraph tags. If your ENML is malformed, the API returns an EDAMUserException with a clear error message indicating which validation rule failed. For notes that contain only plain text or simple HTML, use the note content template shown in the code example — it handles the boilerplate and escapes text content correctly. To read an existing note's content, call noteStore.getNoteContent(noteGuid), which returns the raw ENML string. Use a standard XML parser to extract text if you need plain-text output.

```
# Python — read notebooks and create a note (evernote_client.py)
import os
from evernote.api.client import EvernoteClient
from evernote.edam.type.ttypes import Note

ACCESS_TOKEN = os.environ['EVERNOTE_ACCESS_TOKEN']
SANDBOX = os.environ.get('EVERNOTE_SANDBOX', 'true').lower() == 'true'

client = EvernoteClient(token=ACCESS_TOKEN, sandbox=SANDBOX)
note_store = client.get_note_store()

# List all notebooks
notebooks = note_store.listNotebooks()
print("Your notebooks:")
for nb in notebooks:
    print(f"  {nb.name} (GUID: {nb.guid})")

# Create a simple note
def create_note(title, plain_text, notebook_guid=None):
    enml_content = (
        '<?xml version="1.0" encoding="UTF-8"?>'
        '<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">'
        f'<en-note><p>{plain_text}</p></en-note>'
    )
    note = Note()
    note.title = title
    note.content = enml_content
    if notebook_guid:
        note.notebookGuid = notebook_guid
    created = note_store.createNote(note)
    print(f"Created note: {created.title} (GUID: {created.guid})")
    return created

# Create a test note in the first available notebook
if notebooks:
    create_note("Replit Test Note", "This note was created from Replit!", notebooks[0].guid)
```

**Expected result:** The script lists your Evernote notebooks with their GUIDs and creates a new test note. The note appears in your Evernote app within a few seconds.

### 5. Search notes and build a search API endpoint

Evernote's search API uses the Evernote Query Language (ENL), a structured search syntax that supports keywords, notebook filters, tag filters, and date ranges. The NoteStore.findNotes() method accepts a NoteFilter object that specifies the search query and optional sorting, plus a ResultSpec that defines which note fields to return. Because note content can be large, you should set includeContent=False in the ResultSpec for list operations and only fetch full content for individual notes with getNoteContent(). Build an Express or Flask endpoint that wraps this search logic and returns results as JSON. This endpoint can power custom search interfaces, AI document retrieval pipelines, or cross-app search features. Deployed as an Autoscale Replit deployment, it provides a personal Evernote search API that you can query from any application. For production workloads, add caching (e.g., a simple in-memory dict with a TTL) to avoid hitting Evernote rate limits on repeated identical searches.

```
// Node.js — Evernote search endpoint (search.js)
const Evernote = require('evernote');
const express = require('express');

const app = express();
app.use(express.json());

const ACCESS_TOKEN = process.env.EVERNOTE_ACCESS_TOKEN;
const SANDBOX = process.env.EVERNOTE_SANDBOX === 'true';

const client = new Evernote.Client({ token: ACCESS_TOKEN, sandbox: SANDBOX });

app.get('/search', async (req, res) => {
  const query = req.query.q;
  if (!query) return res.status(400).json({ error: 'q parameter required' });
  try {
    const noteStore = await client.getNoteStore();
    const filter = new Evernote.Types.NoteFilter({ words: query });
    const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
      includeTitle: true,
      includeNotebookGuid: true,
      includeCreated: true
    });
    const result = await noteStore.findNotesMetadata(filter, 0, 10, spec);
    const notes = result.notes.map(n => ({
      guid: n.guid,
      title: n.title,
      notebook: n.notebookGuid,
      created: new Date(n.created).toISOString()
    }));
    res.json({ query, count: notes.length, notes });
  } catch (err) {
    console.error('Evernote search error:', err);
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, '0.0.0.0', () => console.log('Evernote search API running'));
```

**Expected result:** A GET to /search?q=your+keyword returns a JSON array of matching notes with titles, GUIDs, and creation dates. The search API is functional and returns results from your Evernote account.

## Best practices

- Always complete the OAuth 1.0a flow in Sandbox first, then request Production activation from Evernote — never test with production credentials
- Store EVERNOTE_ACCESS_TOKEN, EVERNOTE_CONSUMER_KEY, and EVERNOTE_CONSUMER_SECRET exclusively in Replit Secrets (lock icon 🔒), never in source files
- Cache notebook GUIDs in your application after the first API call — they rarely change and fetching them on every request wastes rate limit quota
- Validate ENML content before submitting to the API to catch formatting errors before they cause EDAMUserException failures
- Request only the note fields you need in ResultSpec — setting includeContent=True for large note lists significantly increases response size and latency
- Implement rate-limit-aware retry logic with exponential backoff since Evernote enforces strict hourly API call limits
- Use the Sandbox environment (EVERNOTE_SANDBOX=true) during all development and testing — switch to production only after your app is approved and verified
- Log API errors with the full EDAMUserException or EDAMSystemException details since they contain actionable error codes and messages

## Use cases

### Automated Research Note Archiver

When your app discovers relevant web content (via RSS feeds, social monitoring, or manual curation), it automatically creates a new Evernote note in a designated research notebook with the article title, source URL, and a summary as the note content. This builds a searchable personal research archive without manual copy-pasting.

Prompt example:

```
Build a Flask endpoint that accepts a JSON payload with article title, URL, and summary text, formats the content as valid ENML, and creates a new Evernote note in a 'Research' notebook. Store EVERNOTE_ACCESS_TOKEN and EVERNOTE_NOTEBOOK_GUID in Replit Secrets.
```

### Daily Summary Note Generator

A scheduled Replit job runs at end of day, queries your other APIs (task managers, calendars, project trackers) to compile a daily summary, and creates a new Evernote note in a 'Daily Log' notebook with completed tasks, meetings attended, and key decisions made. This creates a searchable daily work log automatically.

Prompt example:

```
Build a Python script that generates a daily summary by combining hardcoded placeholder data (replace with your APIs), formats it as ENML with headings and bullet lists, and uses the Evernote SDK to create a dated note in a specific notebook. Schedule it to run daily using Replit's scheduled deployments.
```

### Note Search and Export API

Expose your Evernote library as a searchable API for your own applications. When users search your app, the Replit backend queries Evernote using the NoteStore search API, retrieves matching notes, strips the ENML markup to return plain text, and sends the results back as JSON. This is useful for building custom note-reading interfaces or AI Q&A tools that use your notes as a knowledge base.

Prompt example:

```
Build an Express server with a GET /search endpoint that accepts a query parameter, uses the Evernote SDK to search notes with that query, fetches the content of the top 5 matches, strips ENML tags to extract plain text, and returns the results as a JSON array with title, content preview, and notebook name.
```

## Troubleshooting

### EDAMUserException: BAD_DATA_FORMAT on note creation

Cause: The ENML content is malformed — it either does not include the required DOCTYPE declaration, uses HTML tags not permitted in ENML, or contains unescaped special characters like < or & in text content.

Solution: Ensure every note content string begins with the ENML DOCTYPE declaration. Replace & with &amp; and < with &lt; in any user-provided text before inserting it into ENML tags. Only use HTML elements listed in the Evernote ENML DTD (en-note, p, br, b, i, a, ul, li, etc.) — remove any div, span, or style tags.

```
# Escape text for ENML
def enml_escape(text):
    return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')

def make_enml(body_text):
    safe = enml_escape(body_text)
    return ('<?xml version="1.0" encoding="UTF-8"?>'
            '<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">'
            f'<en-note><p>{safe}</p></en-note>')
```

### OAuth flow returns 'invalid oauth_verifier' or callback URL mismatch error

Cause: The callback URL used when requesting the token does not match the one registered in the Evernote Developer portal, or the oauth_verifier query parameter was not correctly passed to the access token exchange call.

Solution: Ensure the callback URL passed to get_request_token() exactly matches a URL registered in your Evernote app settings. Check that your /oauth/callback route reads oauth_verifier from the query string correctly. In development, use your Replit preview URL (visible in the webview address bar).

### All API calls return EDAMSystemException: RATE_LIMIT_REACHED

Cause: You have exceeded Evernote's API rate limit for your access token or application. Evernote enforces hourly and daily rate limits.

Solution: Add exponential backoff retry logic for API calls. Cache frequently accessed data (like notebook lists) in memory and refresh only periodically. Avoid making API calls in tight loops — batch operations where possible.

```
import time
def call_with_retry(fn, *args, max_retries=3):
    for attempt in range(max_retries):
        try:
            return fn(*args)
        except Exception as e:
            if 'RATE_LIMIT' in str(e) and attempt < max_retries - 1:
                time.sleep(2 ** attempt)
            else:
                raise
```

### Note content is fetched but shows raw ENML XML instead of plain text

Cause: The getNoteContent API returns raw ENML XML. Your code is returning this directly instead of parsing out the text content.

Solution: Parse the ENML with an XML library (xml.etree.ElementTree in Python, or a DOMParser in Node.js) and extract text nodes. Strip all XML tags to get plain text, or use a regex to remove ENML markup for simple cases.

```
import xml.etree.ElementTree as ET
def enml_to_text(enml):
    # Remove DOCTYPE declaration which confuses parsers
    content = enml.split('<en-note')[1] if '<en-note' in enml else enml
    content = '<en-note' + content
    root = ET.fromstring(content)
    return ' '.join(root.itertext())
```

## Frequently asked questions

### How do I connect Replit to Evernote?

Register an app at dev.evernote.com, complete the OAuth 1.0a authorization flow by running a temporary Flask or Express server on Replit, copy the resulting access token to Replit Secrets (lock icon 🔒), and use the Evernote SDK (evernote3 for Python, evernote for Node.js) to make API calls from your server-side code.

### Does Replit work with Evernote for free?

Evernote provides API access for free during development using their Sandbox environment. Production API access requires approval from Evernote's developer program. Replit's free tier is sufficient for development and testing, but you will need a paid Replit plan for always-on Autoscale deployments.

### Why is OAuth 1.0a required for Evernote instead of OAuth 2.0?

Evernote has used OAuth 1.0a since the API was first released and has not migrated to OAuth 2.0. This means you need to handle request token signing and the three-step handshake rather than the simpler OAuth 2.0 code flow. Using the official Evernote SDK handles the signing details automatically.

### What is ENML and why does Evernote require it?

ENML (Evernote Markup Language) is an XML-based format derived from XHTML that Evernote uses to store note content. It is required because it allows Evernote to render notes consistently across all platforms while supporting rich formatting, attachments, and media. Your API calls must produce valid ENML — malformed content will be rejected with a BAD_DATA_FORMAT error.

### How do I store the Evernote access token in Replit?

After completing the OAuth flow, copy the access token string and add it to Replit Secrets as EVERNOTE_ACCESS_TOKEN via the lock icon 🔒 in the sidebar. Access it in Python with os.environ['EVERNOTE_ACCESS_TOKEN'] or in Node.js with process.env.EVERNOTE_ACCESS_TOKEN. Never store it in your source code.

---

Source: https://www.rapidevelopers.com/replit-integration/evernote
© RapidDev — https://www.rapidevelopers.com/replit-integration/evernote
