# How to Integrate Bolt.new with Todoist

- Tool: Bolt.new
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: April 2026

## TL;DR

Integrate Bolt.new with Todoist using your personal API token from Settings → Integrations → Developer and the Todoist REST API v2. Build custom task dashboards, create tasks with natural language due dates, manage projects and labels — all through a Next.js API route that keeps your token server-side. Todoist's clean API and natural language date parsing make it one of the easiest productivity integrations to build.

## Building Custom Task Dashboards and Automations with Todoist and Bolt.new

Todoist's REST API v2 is one of the cleanest and best-documented productivity APIs available. It gives you full access to your tasks, projects, sections, labels, comments, and reminders. With this API you can build experiences that Todoist's own interface does not offer — consolidated multi-project dashboards, automated task creation triggered by form submissions or external events, custom Kanban boards with your own column logic, and reporting views formatted exactly for your workflow.

One of Todoist's standout features for Bolt integrations is natural language due date processing. When you create or update a task, you can pass the due_string field with values like 'every Monday at 9am', 'tomorrow at noon', or 'in 3 days' — and Todoist parses them into proper timestamps automatically. This means users of your Bolt app can type dates in plain English rather than interacting with a date picker, resulting in a much more natural UX with minimal frontend code.

Because the Todoist integration relies entirely on outbound HTTP requests, you can build and test the full feature set — reading tasks, creating tasks, updating priorities, adding comments — inside Bolt's WebContainer without deploying first. The only capability that requires a deployed URL is Todoist webhooks, which push real-time notifications to your server when tasks are added, completed, or updated. For most task dashboards, periodic polling every 30 seconds is a perfectly adequate alternative that avoids the deployment requirement during development.

## Before you start

- A Todoist account with at least one project containing tasks
- Your Todoist API token (found at todoist.com/app/settings/integrations/developer — scroll to API token section)
- A Next.js project in Bolt.new (prompt 'Create a Next.js app' to get started)
- Your project IDs (visible in the URL when you open a project in Todoist: app.todoist.com/app/project/{projectId})

## Step-by-step guide

### 1. Get your Todoist API token and set up environment variables

Todoist provides a personal API token that grants access to all your data. To find it, log into Todoist and navigate to Settings (click your avatar bottom-left) → Integrations → Developer. Your API token is displayed under the 'API token' heading. Copy it — this is what authenticates every API call your Bolt app makes to Todoist.

Unlike some APIs that use API keys as query parameters, Todoist uses standard HTTP Bearer token authentication. Every request includes an Authorization header in the format 'Bearer {your-token}'. This is different from ClickUp (which uses the raw token without 'Bearer') — with Todoist you do need the 'Bearer' prefix.

Create a .env.local file in your Bolt project root with your token. Since the token must stay server-side, do not prefix it with NEXT_PUBLIC_ and do not prefix it with VITE_. Access it only inside Next.js API routes using process.env.TODOIST_API_TOKEN. Your project ID is safe to expose publicly (it is just an identifier), but for consistency keep it in .env as well.

You can find your project IDs by opening a project in Todoist — the project ID appears in the URL: app.todoist.com/app/project/2349123456. You can also fetch all your projects programmatically using the /projects endpoint once your token is configured.

```
// .env.local
TODOIST_API_TOKEN=your_api_token_here
TODOIST_PROJECT_ID=your_default_project_id
```

**Expected result:** Your .env.local file is created, and calling /api/todoist/projects returns a JSON array of all your Todoist projects with their IDs, names, and colors.

### 2. Fetch active tasks and display them in a dashboard

The Todoist REST API v2 uses a simple, intuitive endpoint structure. To fetch tasks, make a GET request to https://api.todoist.com/rest/v2/tasks. Without any query parameters, this returns all active (not completed) tasks across all projects. You can filter using query parameters: project_id to limit to a specific project, label to filter by label name, filter to use Todoist's native filter query language (e.g., 'today | overdue' shows today's and overdue tasks), assignee_id for shared projects, and priority for filtering by priority level (1=normal, 2=medium, 3=high, 4=urgent — note this is the reverse of ClickUp's numbering).

Task objects in the response include: id, content (the task name), description (plain text notes), project_id, section_id, parent_id (for subtasks), order, labels (array of label names), priority (1-4), due (object with date, datetime, string, timezone), url (link to task in Todoist app), comment_count, creator_id, created_at, and assignee_id.

The due object is particularly useful — it contains both the human-readable string (like 'every Monday') and the parsed date/datetime values. Use due.date for all-day tasks and due.datetime for tasks with specific times.

Since these are outbound API calls, they work perfectly in Bolt's WebContainer preview without any deployment needed. You can build and fully test the entire read-only dashboard in Bolt before deploying.

```
// app/api/todoist/tasks/route.ts
import { NextRequest, NextResponse } from 'next/server';

const TODOIST_API = 'https://api.todoist.com/rest/v2';
const API_TOKEN = process.env.TODOIST_API_TOKEN;

type TodoistTask = {
  id: string;
  content: string;
  description: string;
  project_id: string;
  priority: number;
  due: { date: string; datetime: string | null; string: string } | null;
  labels: string[];
  url: string;
};

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const projectId = searchParams.get('project_id');
  const filter = searchParams.get('filter');

  const params = new URLSearchParams();
  if (projectId) params.set('project_id', projectId);
  if (filter) params.set('filter', filter);

  const url = `${TODOIST_API}/tasks${params.toString() ? '?' + params.toString() : ''}`;

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${API_TOKEN}` },
  });

  if (!response.ok) {
    return NextResponse.json(
      { error: `Todoist API error: ${response.status}` },
      { status: response.status }
    );
  }

  const tasks: TodoistTask[] = await response.json();

  // Sort by priority descending, then by due date
  const sorted = tasks.sort((a, b) => {
    if (b.priority !== a.priority) return b.priority - a.priority;
    if (!a.due && !b.due) return 0;
    if (!a.due) return 1;
    if (!b.due) return -1;
    return new Date(a.due.date).getTime() - new Date(b.due.date).getTime();
  });

  return NextResponse.json({ tasks: sorted, count: sorted.length });
}
```

**Expected result:** The dashboard displays your Todoist tasks as color-coded cards sorted by priority, with filter buttons that switch between Today, Next 7 Days, and All Tasks views.

### 3. Create tasks with natural language due dates

Creating tasks in Todoist via the API is where the integration really shines. The POST /tasks endpoint accepts the content field (the task name, which can include Markdown formatting) and a rich set of optional fields. The most powerful optional field is due_string — Todoist's natural language parser will convert values like 'every Monday at 9am', 'next Friday', 'in 2 weeks', 'tomorrow', or 'June 15' into proper recurring or one-time due dates. This means you can build forms where users type natural language dates instead of using a date picker.

Other useful fields for task creation: description (plain text or Markdown notes appended below the task), project_id (defaults to Inbox if omitted), section_id (places the task in a specific section within the project), labels (array of label name strings — labels are created automatically if they don't exist), priority (1=normal, 2=medium, 3=high, 4=urgent), parent_id (to create a subtask under an existing task), and assignee_id (for shared projects with multiple collaborators).

To update an existing task, use POST /tasks/{taskId} with only the fields you want to change. To mark a task complete, send POST /tasks/{taskId}/close — the response is an empty 204 No Content. To reopen a completed task, use POST /tasks/{taskId}/reopen.

All these mutation operations work fine as outbound HTTP requests from Bolt's WebContainer. You can create, update, and close tasks in development without deploying. Webhook notifications — where Todoist pushes events to your server — require a deployed URL, but you do not need webhooks to build a fully functional task creation interface.

```
// app/api/todoist/tasks/create/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { randomUUID } from 'crypto';

const TODOIST_API = 'https://api.todoist.com/rest/v2';
const API_TOKEN = process.env.TODOIST_API_TOKEN;
const DEFAULT_PROJECT_ID = process.env.TODOIST_PROJECT_ID;

export async function POST(request: NextRequest) {
  const {
    content,
    description,
    project_id,
    priority,
    due_string,
    labels,
    parent_id,
  } = await request.json();

  if (!content || typeof content !== 'string' || content.trim() === '') {
    return NextResponse.json({ error: 'content is required' }, { status: 400 });
  }

  const taskBody: Record<string, unknown> = {
    content: content.trim(),
    project_id: project_id ?? DEFAULT_PROJECT_ID,
    priority: priority ?? 1,
  };

  if (description) taskBody.description = description;
  if (due_string) taskBody.due_string = due_string;
  if (labels && Array.isArray(labels)) taskBody.labels = labels;
  if (parent_id) taskBody.parent_id = parent_id;

  const response = await fetch(`${TODOIST_API}/tasks`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_TOKEN}`,
      'Content-Type': 'application/json',
      'X-Request-Id': randomUUID(), // Idempotency key for safe retries
    },
    body: JSON.stringify(taskBody),
  });

  if (!response.ok) {
    const errorText = await response.text();
    return NextResponse.json(
      { error: errorText || `Todoist API error: ${response.status}` },
      { status: response.status }
    );
  }

  const task = await response.json();
  return NextResponse.json({ id: task.id, content: task.content, url: task.url });
}
```

**Expected result:** Submitting the task creation form creates a new task in Todoist with the specified due date, priority, and labels. Tasks with due_string values like 'every Monday' appear as recurring tasks in Todoist.

### 4. Deploy to Netlify or Vercel and set up Todoist webhooks

While task reading and creation work perfectly in Bolt's WebContainer preview, Todoist webhooks require a publicly accessible URL to deliver real-time notifications. Webhooks let Todoist push events to your server the instant something changes — a task is completed, a new task is added, a comment is posted — rather than your app polling for changes. This is essential for keeping a dashboard up-to-date in real time.

To deploy your Bolt app to Netlify, click the Publish button in the top-right corner of Bolt, or go to Settings → Applications → Connect Netlify. After deployment, you will have a public URL like your-app.netlify.app. For Vercel, use Bolt's GitHub integration to push your code to a repository, then connect that repository in Vercel's dashboard.

Once deployed, register a Todoist webhook through the Todoist developer console at developer.todoist.com/appconsole. You will need to create a Todoist app to get a client ID and secret for webhook signature verification. Set the webhook URL to your-app.netlify.app/api/todoist/webhook. Subscribe to the event types you care about: item:added, item:completed, item:updated, item:deleted.

When Todoist sends a webhook, it includes an X-Todoist-Hmac-SHA256 header containing an HMAC-SHA256 signature of the request body. You must verify this signature in your webhook handler using your app's client secret to prevent processing fake events. The webhook payload includes the event_name, user_id, and event_data object containing the full task or project data.

For development testing without deploying, use a tool like ngrok to expose your local Bolt dev server to the internet temporarily, then register that ngrok URL as your webhook endpoint.

```
// app/api/todoist/webhook/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createHmac } from 'crypto';

const CLIENT_SECRET = process.env.TODOIST_CLIENT_SECRET ?? '';

function verifyTodoistSignature(body: string, signature: string): boolean {
  const expected = createHmac('sha256', CLIENT_SECRET)
    .update(body)
    .digest('base64');
  return expected === signature;
}

export async function POST(request: NextRequest) {
  const signature = request.headers.get('x-todoist-hmac-sha256') ?? '';
  const rawBody = await request.text();

  if (!verifyTodoistSignature(rawBody, signature)) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  const payload = JSON.parse(rawBody);
  const { event_name, event_data } = payload;

  console.log(`Todoist webhook: ${event_name}`, event_data);

  switch (event_name) {
    case 'item:added':
      // Handle new task creation
      break;
    case 'item:completed':
      // Handle task completion
      break;
    case 'item:updated':
      // Handle task update
      break;
    default:
      break;
  }

  return NextResponse.json({ received: true });
}
```

**Expected result:** After deploying and registering the webhook URL in the Todoist developer console, your app receives real-time notifications when tasks are added or completed, and the webhook handler logs the event data to your server logs.

## Best practices

- Never expose your Todoist API token in client-side code — always access it via process.env.TODOIST_API_TOKEN in server-side Next.js API routes only, never with a NEXT_PUBLIC_ prefix.
- Include X-Request-Id with a unique UUID on every POST request to enable idempotent task creation — this prevents duplicate tasks if a network error causes your app to retry the request.
- Cache project and label data on the server side and refresh it every few minutes rather than fetching on every request — projects and labels rarely change and count against your API rate limits.
- Use Todoist's filter query language (the filter parameter) instead of fetching all tasks and filtering client-side — passing filter=today is far more efficient than downloading your entire task list.
- Handle the 429 Too Many Requests response by checking the Retry-After header and waiting that many seconds before retrying — Todoist enforces rate limits based on your plan tier.
- For real-time task updates, prefer Todoist webhooks over polling once you deploy — webhooks push events instantly rather than waiting for your next poll interval, reducing API usage and latency.
- Validate and sanitize the content field before sending to Todoist — very long strings or special characters can cause unexpected behavior; trim whitespace and enforce a reasonable character limit in your form validation.
- Use Todoist's sync API (rather than the REST API) for bulk operations like importing many tasks at once — the sync API processes arrays of commands in a single request, much more efficient than individual POST calls.

## Use cases

### Personal productivity dashboard

Build a consolidated view of all your Todoist tasks due today, sorted by project and priority, with overdue tasks highlighted in red. The dashboard refreshes every 30 seconds and shows a completion progress bar for the current day.

Prompt example:

```
Create a Todoist daily dashboard in Next.js. Fetch all tasks due today or overdue using the Todoist REST API v2. Group them by project name and sort by priority (priority 4 = urgent at top). Show overdue tasks with a red badge. Add a progress bar showing completed vs total tasks for today. Auto-refresh every 30 seconds.
```

### Automated task creation from a form

When a user submits a contact form, bug report, or feature request in your app, automatically create a Todoist task in the appropriate project with the form data as the task content, set the priority based on urgency, and use Todoist's natural language parser to set a due date like 'in 2 business days'.

Prompt example:

```
Add a Todoist integration to my feedback form. When a user submits a bug report with a title, description, and severity (low/medium/high/critical), create a Todoist task in project ID [PROJECT_ID] with the title as the task content, add the description as a comment, and set priority: critical=4, high=3, medium=2, low=1. Set due_string to 'tomorrow' for critical and 'in 3 days' for others.
```

### Recurring task template generator

Build an interface where users select a workflow template (like 'Weekly Review' or 'New Client Onboarding') and your app creates a batch of recurring tasks in Todoist with properly structured due dates, labels, and project assignments — all in one click.

Prompt example:

```
Build a Todoist task template page. Create a /api/todoist/batch-create route that accepts an array of task objects and creates them all in Todoist. Build a UI with a dropdown of templates (Weekly Review, Project Kickoff, Content Calendar). When a template is selected, show the tasks it will create and a 'Create All Tasks' button. Each task should have a name, project ID, priority, and due_string.
```

## Troubleshooting

### All API calls return 401 Unauthorized

Cause: The Authorization header is incorrectly formatted, the API token is invalid, or the .env variable is not being read. Todoist requires the 'Bearer' prefix in the Authorization header — unlike some other APIs that use raw tokens.

Solution: Verify that the Authorization header in your API route reads exactly: Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}`. Confirm the token value in your .env.local matches the current token in Todoist Settings → Integrations → Developer. Regenerate the token if it was recently changed.

```
// Correct format — with Bearer prefix:
headers: { Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}` }

// Wrong — missing Bearer prefix:
headers: { Authorization: process.env.TODOIST_API_TOKEN }
```

### Task creation returns 400 Bad Request with no helpful error message

Cause: The request body is missing the required content field, or the content value is an empty string. Todoist also returns 400 if project_id points to a project that does not exist or that your token cannot access.

Solution: Ensure the request body includes a non-empty content field as a string. Verify your TODOIST_PROJECT_ID environment variable contains a valid project ID. Call GET /api/todoist/projects to get a list of valid project IDs for your account.

### API calls work in Bolt preview but Todoist webhooks are never received

Cause: Todoist webhooks require a publicly accessible HTTPS URL. Bolt's WebContainer development environment does not have a public URL — it runs entirely in the browser and cannot receive inbound HTTP requests from external services.

Solution: Deploy your app to Netlify or Vercel first using Bolt's publish workflow. After deployment, register your production URL (e.g., your-app.netlify.app/api/todoist/webhook) in the Todoist developer console. For local development testing, use ngrok to temporarily expose your local server to the internet.

### Natural language due dates are not being parsed correctly — tasks have no due date or the wrong date

Cause: The due_string value may be in a format Todoist's NLP parser does not recognize, or the timezone is not set correctly. Todoist parses dates relative to the user's timezone configured in their account settings.

Solution: Use the due_lang parameter to explicitly specify the language (e.g., 'en' for English). Test your due_string values in the Todoist app first by typing them directly into the task creation field. Common reliable formats: 'tomorrow', 'next Monday', 'June 15', 'every week on Monday'. Avoid ambiguous formats like '3/15' which could be March or May depending on locale.

```
// Include explicit language for reliable parsing:
const taskBody = {
  content: 'Review analytics report',
  due_string: 'every Monday at 9am',
  due_lang: 'en',
};
```

## Frequently asked questions

### How do I connect Bolt.new to Todoist?

Get your personal API token from Todoist Settings → Integrations → Developer and add it as TODOIST_API_TOKEN in your Bolt project's .env.local file. Then use Bolt chat to generate Next.js API routes that call the Todoist REST API v2 with Bearer token authentication. All outbound API calls — reading and creating tasks — work in Bolt's development preview without deploying.

### Does Bolt.new support Todoist natural language due dates?

Yes. When creating tasks via the Todoist API, include the due_string field with any natural language value like 'tomorrow at 9am', 'every Monday', or 'next Friday'. Todoist's server-side NLP parser converts these into proper due dates automatically. You can also include due_lang: 'en' to ensure English parsing regardless of your account's default language setting.

### Can I test the Todoist integration in Bolt's preview before deploying?

Yes, for reading and writing tasks. Outbound API calls to Todoist work perfectly in Bolt's WebContainer preview — you can fetch tasks, create tasks, update priorities, add comments, and close tasks all without deploying. The only limitation is incoming webhooks: if you want Todoist to notify your app in real-time when tasks change, you need to deploy to a public URL first.

### How do I find my Todoist project IDs?

Open any project in Todoist and look at the URL in your browser — the project ID is the long number at the end, for example app.todoist.com/app/project/2349123456. You can also call GET https://api.todoist.com/rest/v2/projects with your API token to retrieve all project IDs, names, and colors programmatically.

### What is Todoist's API rate limit?

Todoist limits API requests based on your account plan. Free accounts can make up to 1,000 requests per 15 minutes. If you exceed the limit, the API returns a 429 status with a Retry-After header indicating how many seconds to wait. For most dashboard use cases making a few dozen calls on page load, this limit is rarely reached.

### How do I deploy a Bolt.new app with Todoist to Netlify?

In Bolt, go to Settings → Applications → Connect Netlify and authorize with OAuth. Click Publish in the top-right corner to deploy your app to a .netlify.app URL. After deployment, add your TODOIST_API_TOKEN and any other environment variables in the Netlify dashboard under Site Settings → Environment Variables. Then redeploy from Netlify or trigger a new Bolt publish to apply the variables.

---

Source: https://www.rapidevelopers.com/bolt-ai-integrations/todoist
© RapidDev — https://www.rapidevelopers.com/bolt-ai-integrations/todoist
