| name | evernote-rate-limits |
| description | Handle Evernote API rate limits effectively.
Use when implementing rate limit handling, optimizing API usage,
or troubleshooting rate limit errors.
Trigger with phrases like "evernote rate limit", "evernote throttling",
"api quota evernote", "rate limit exceeded".
|
| allowed-tools | Read, Write, Edit, Grep |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Evernote Rate Limits
Overview
Evernote enforces rate limits per API key, per user, per hour. Understanding and handling these limits is essential for production integrations.
Prerequisites
- Evernote SDK setup
- Understanding of async/await patterns
- Error handling implementation
Rate Limit Structure
| Scope | Limit Window | Error |
|---|
| Per API key | 1 hour | EDAMSystemException |
| Per user | 1 hour | EDAMSystemException |
| Combined | Per key + per user | RATE_LIMIT_REACHED |
Key points:
- Limits are NOT publicly documented (intentionally)
- Hitting the limit returns
rateLimitDuration (seconds to wait)
- Limits are generally generous for normal usage
- Initial sync boost available (24 hours, must be requested)
Instructions
Step 1: Rate Limit Handler
class RateLimitHandler {
constructor(options = {}) {
this.maxRetries = options.maxRetries || 3;
this.onRateLimit = options.onRateLimit || (() => {});
this.requestQueue = [];
this.isProcessing = false;
this.minDelay = options.minDelay || 100;
}
async execute(operation) {
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (this.isRateLimitError(error)) {
const waitTime = error.rateLimitDuration * 1000;
this.onRateLimit({
attempt: attempt + 1,
waitTime,
willRetry: attempt < . -
});
(attempt < . - ) {
.();
.(waitTime);
;
}
}
error;
}
}
}
() {
( {
..({ operation, resolve, reject });
.();
});
}
() {
(. || .. === ) {
;
}
. = ;
(.. > ) {
{ operation, resolve, reject } = ..();
{
result = .(operation);
(result);
} (error) {
(error);
}
(.. > ) {
.(.);
}
}
. = ;
}
() {
error. === && error. !== ;
}
() {
( (resolve, ms));
}
}
. = ;
Step 2: Rate-Limited Client Wrapper
const Evernote = require('evernote');
const RateLimitHandler = require('../utils/rate-limiter');
class RateLimitedEvernoteClient {
constructor(accessToken, options = {}) {
this.client = new Evernote.Client({
token: accessToken,
sandbox: options.sandbox || false
});
this.rateLimiter = new RateLimitHandler({
maxRetries: options.maxRetries || 3,
minDelay: options.minDelay || 100,
onRateLimit: (info) => {
console.log(`[Rate Limit] Attempt ${info.attempt}, wait ${info.waitTime}ms`);
if (options.onRateLimit) {
options.onRateLimit(info);
}
}
});
this._noteStore = null;
}
get noteStore() {
if (!.) {
originalStore = ..();
. = .(originalStore);
}
.;
}
() {
rateLimiter = .;
(store, {
() {
original = target[prop];
( original !== ) {
original;
}
{
rateLimiter.( original.(target, args));
};
}
});
}
}
. = ;
Step 3: Batch Operations with Rate Limiting
class BatchProcessor {
constructor(rateLimiter, options = {}) {
this.rateLimiter = rateLimiter;
this.batchSize = options.batchSize || 10;
this.delayBetweenBatches = options.delayBetweenBatches || 1000;
this.onProgress = options.onProgress || (() => {});
}
async processBatch(items, operation) {
const results = [];
const total = items.length;
let processed = 0;
const batches = this.chunkArray(items, this.batchSize);
for (const batch of batches) {
const batchResults = await Promise.all(
batch.map(item =>
this.rateLimiter.enqueue(async () => {
{
result = (item);
{ : , item, result };
} (error) {
{ : , item, : error. };
}
})
)
);
results.(...batchResults);
processed += batch.;
.({
processed,
total,
: .((processed / total) * )
});
(processed < total) {
.(.);
}
}
{
: results.,
: results.( r.).,
: results.( !r.).,
results
};
}
() {
chunks = [];
( i = ; i < array.; i += size) {
chunks.(array.(i, i + size));
}
chunks;
}
() {
( (resolve, ms));
}
}
. = ;
Step 4: Avoiding Rate Limits
class EvernoteOptimizer {
constructor(noteStore) {
this.noteStore = noteStore;
this.notebookCache = null;
this.tagCache = null;
this.cacheExpiry = 5 * 60 * 1000;
}
async badPattern() {
const notebooks = await this.noteStore.listNotebooks();
const notebooks2 = await this.noteStore.listNotebooks();
}
async getNotebooks(forceRefresh = false) {
if (!forceRefresh && this.notebookCache && Date.now() < this.notebookCacheExpiry) {
return this.;
}
. = ..();
. = .() + .;
.;
}
() {
..(guid, , , , );
}
() {
..(
guid,
options. || ,
options. || ,
options. || ,
options. ||
);
}
() {
() {
state = ..();
();
}
}
() {
state = ..();
(state. === lastUpdateCount) {
{ : };
}
chunks = ..(
lastUpdateCount,
,
{
: ,
: ,
:
}
);
{
: ,
: state.,
chunks
};
}
() {
note = ..(noteGuid, , , , );
( resource note. || []) {
..(resource., , , , );
}
}
() {
..(
noteGuid,
,
,
,
);
}
}
Step 5: Rate Limit Monitoring
class RateLimitMonitor {
constructor() {
this.history = [];
this.windowSize = 60 * 60 * 1000;
}
recordRequest() {
const now = Date.now();
this.history.push(now);
this.pruneOldEntries(now);
}
recordRateLimit(rateLimitDuration) {
this.history.push({
timestamp: Date.now(),
rateLimited: true,
duration: rateLimitDuration
});
}
pruneOldEntries(now) {
const cutoff = now - this.windowSize;
this.history = this.history.filter(entry => {
const timestamp = typeof entry === 'number' ? entry : entry.timestamp;
timestamp > cutoff;
});
}
() {
now = .();
.(now);
requests = ..( e === );
rateLimits = ..( e.);
{
: requests.,
: rateLimits.,
: (requests. / ).(),
: rateLimits. > ?
(rateLimits[rateLimits. - ].) :
};
}
() {
stats = .();
stats. > || stats. > ;
}
}
. = ;
Step 6: Usage Example
const RateLimitedEvernoteClient = require('./services/rate-limited-client');
const BatchProcessor = require('./utils/batch-processor');
const RateLimitMonitor = require('./utils/rate-monitor');
async function main() {
const monitor = new RateLimitMonitor();
const client = new RateLimitedEvernoteClient(
process.env.EVERNOTE_ACCESS_TOKEN,
{
sandbox: true,
maxRetries: 3,
minDelay: 200,
onRateLimit: (info) => {
monitor.recordRateLimit(info.waitTime / 1000);
console.log('Rate limit stats:', monitor.getStats());
}
}
);
const noteStore = client.noteStore;
const notebooks = await noteStore.listNotebooks();
console.log(, notebooks.);
processor = (client., {
: ,
: ,
: {
.();
}
});
noteGuids = [, , ];
results = processor.(
noteGuids,
noteStore.(guid, , , , )
);
.(, results);
.(, monitor.());
}
().(.);
Output
- Automatic retry with exponential backoff
- Request queuing to prevent bursts
- Batch processing with progress tracking
- Rate limit monitoring and statistics
- Optimized API usage patterns
Best Practices Summary
| Do | Don't |
|---|
| Cache frequently accessed data | Make duplicate API calls |
| Request only needed data | Use withResourcesData when not needed |
| Use webhooks for change detection | Poll getSyncState repeatedly |
| Batch operations with delays | Fire many requests simultaneously |
| Handle rateLimitDuration | Retry immediately after rate limit |
Error Handling
| Scenario | Response |
|---|
| First rate limit | Wait rateLimitDuration, retry |
| Repeated rate limits | Increase base delay, reduce batch size |
| Rate limit + other error | Handle other error first |
| Rate limit on initial sync | Request rate limit boost |
Resources
Next Steps
For security considerations, see evernote-security-basics.