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.
Integration method
Bolt generates the Todoist integration through conversation — you describe the task board or automation you want and Bolt writes the API route and React component code. Todoist uses a simple Bearer token for authentication, so the full integration can be built and tested in Bolt's development environment using outbound API calls. All API calls flow through server-side Next.js routes to keep your token out of the browser bundle.
Prerequisites
- 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
Get your Todoist API token and set up environment variables
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.
Create a Next.js app with a Todoist integration. Add a .env.local file with TODOIST_API_TOKEN and TODOIST_PROJECT_ID as placeholder variables. Create a /api/todoist/projects route that fetches all my Todoist projects using the Todoist REST API v2 and returns them as JSON. Use Bearer token authentication.
Paste this in Bolt.new chat
1// .env.local2TODOIST_API_TOKEN=your_api_token_here3TODOIST_PROJECT_ID=your_default_project_idPro tip: Your Todoist API token is a 40-character hex string. If you share your Bolt project or push to GitHub, make sure .env.local is in your .gitignore file — it should be by default in Next.js projects.
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.
Fetch active tasks and display them in a dashboard
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.
Create a Todoist task dashboard page in Next.js. Build a /api/todoist/tasks route that fetches active tasks from the Todoist API, with optional project_id and filter query parameters. Build a React component that displays tasks as cards showing: task content, project name (color-coded dot), priority badge (urgent=red, high=orange, medium=yellow, normal=gray), due date (red if overdue), and labels as chips. Add filter buttons for Today, Next 7 Days, and All Tasks.
Paste this in Bolt.new chat
1// app/api/todoist/tasks/route.ts2import { NextRequest, NextResponse } from 'next/server';34const TODOIST_API = 'https://api.todoist.com/rest/v2';5const API_TOKEN = process.env.TODOIST_API_TOKEN;67type TodoistTask = {8 id: string;9 content: string;10 description: string;11 project_id: string;12 priority: number;13 due: { date: string; datetime: string | null; string: string } | null;14 labels: string[];15 url: string;16};1718export async function GET(request: NextRequest) {19 const { searchParams } = new URL(request.url);20 const projectId = searchParams.get('project_id');21 const filter = searchParams.get('filter');2223 const params = new URLSearchParams();24 if (projectId) params.set('project_id', projectId);25 if (filter) params.set('filter', filter);2627 const url = `${TODOIST_API}/tasks${params.toString() ? '?' + params.toString() : ''}`;2829 const response = await fetch(url, {30 headers: { Authorization: `Bearer ${API_TOKEN}` },31 });3233 if (!response.ok) {34 return NextResponse.json(35 { error: `Todoist API error: ${response.status}` },36 { status: response.status }37 );38 }3940 const tasks: TodoistTask[] = await response.json();4142 // Sort by priority descending, then by due date43 const sorted = tasks.sort((a, b) => {44 if (b.priority !== a.priority) return b.priority - a.priority;45 if (!a.due && !b.due) return 0;46 if (!a.due) return 1;47 if (!b.due) return -1;48 return new Date(a.due.date).getTime() - new Date(b.due.date).getTime();49 });5051 return NextResponse.json({ tasks: sorted, count: sorted.length });52}Pro tip: Use Todoist's built-in filter language for powerful queries. Pass filter=today to get today's tasks, filter=overdue for overdue tasks, or filter=p1 for urgent priority tasks. See the Todoist filter documentation for the full syntax.
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.
Create tasks with natural language due dates
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.
Add task creation to my Todoist integration. Create a /api/todoist/tasks/create POST route that creates a task with: content (required), description (optional), project_id (optional, defaults to env var), priority (1-4, defaults to 1), due_string (natural language date like 'tomorrow' or 'every Monday'), and labels (array of strings). Create a simple task creation form with all these fields. Add a /api/todoist/tasks/[id]/close route for marking tasks complete.
Paste this in Bolt.new chat
1// app/api/todoist/tasks/create/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { randomUUID } from 'crypto';45const TODOIST_API = 'https://api.todoist.com/rest/v2';6const API_TOKEN = process.env.TODOIST_API_TOKEN;7const DEFAULT_PROJECT_ID = process.env.TODOIST_PROJECT_ID;89export async function POST(request: NextRequest) {10 const {11 content,12 description,13 project_id,14 priority,15 due_string,16 labels,17 parent_id,18 } = await request.json();1920 if (!content || typeof content !== 'string' || content.trim() === '') {21 return NextResponse.json({ error: 'content is required' }, { status: 400 });22 }2324 const taskBody: Record<string, unknown> = {25 content: content.trim(),26 project_id: project_id ?? DEFAULT_PROJECT_ID,27 priority: priority ?? 1,28 };2930 if (description) taskBody.description = description;31 if (due_string) taskBody.due_string = due_string;32 if (labels && Array.isArray(labels)) taskBody.labels = labels;33 if (parent_id) taskBody.parent_id = parent_id;3435 const response = await fetch(`${TODOIST_API}/tasks`, {36 method: 'POST',37 headers: {38 Authorization: `Bearer ${API_TOKEN}`,39 'Content-Type': 'application/json',40 'X-Request-Id': randomUUID(), // Idempotency key for safe retries41 },42 body: JSON.stringify(taskBody),43 });4445 if (!response.ok) {46 const errorText = await response.text();47 return NextResponse.json(48 { error: errorText || `Todoist API error: ${response.status}` },49 { status: response.status }50 );51 }5253 const task = await response.json();54 return NextResponse.json({ id: task.id, content: task.content, url: task.url });55}Pro tip: Always include an X-Request-Id header with a unique UUID when creating tasks. Todoist uses this as an idempotency key — if your request fails and you retry, the same task will not be created twice.
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.
Deploy to Netlify or Vercel and set up Todoist webhooks
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.
Add a Todoist webhook handler to my app at /api/todoist/webhook. The handler should: (1) verify the X-Todoist-Hmac-SHA256 signature using the TODOIST_CLIENT_SECRET environment variable, (2) parse the event_name and event_data from the payload, (3) handle item:completed and item:added events by logging them and returning 200. Add TODOIST_CLIENT_SECRET to the .env.local file.
Paste this in Bolt.new chat
1// app/api/todoist/webhook/route.ts2import { NextRequest, NextResponse } from 'next/server';3import { createHmac } from 'crypto';45const CLIENT_SECRET = process.env.TODOIST_CLIENT_SECRET ?? '';67function verifyTodoistSignature(body: string, signature: string): boolean {8 const expected = createHmac('sha256', CLIENT_SECRET)9 .update(body)10 .digest('base64');11 return expected === signature;12}1314export async function POST(request: NextRequest) {15 const signature = request.headers.get('x-todoist-hmac-sha256') ?? '';16 const rawBody = await request.text();1718 if (!verifyTodoistSignature(rawBody, signature)) {19 return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });20 }2122 const payload = JSON.parse(rawBody);23 const { event_name, event_data } = payload;2425 console.log(`Todoist webhook: ${event_name}`, event_data);2627 switch (event_name) {28 case 'item:added':29 // Handle new task creation30 break;31 case 'item:completed':32 // Handle task completion33 break;34 case 'item:updated':35 // Handle task update36 break;37 default:38 break;39 }4041 return NextResponse.json({ received: true });42}Pro tip: Todoist webhooks are only sent over HTTPS and must receive a 200 response within 7 seconds. If your handler takes longer, Todoist may retry the webhook. Keep webhook processing fast by acknowledging immediately and doing any heavy work asynchronously.
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.
Common 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.
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.
Copy this prompt to try it in Bolt.new
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'.
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.
Copy this prompt to try it in Bolt.new
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.
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.
Copy this prompt to try it in Bolt.new
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.
1// Correct format — with Bearer prefix:2headers: { Authorization: `Bearer ${process.env.TODOIST_API_TOKEN}` }34// Wrong — missing Bearer prefix:5headers: { 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.
1// Include explicit language for reliable parsing:2const taskBody = {3 content: 'Review analytics report',4 due_string: 'every Monday at 9am',5 due_lang: 'en',6};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.
Alternatives
Trello is visual Kanban-first with simple API key + token auth, better for teams that prefer board-based project management over Todoist's list-based personal productivity approach.
Asana offers stronger team collaboration features and portfolio management with a well-documented REST API, making it a better fit for larger teams than Todoist's individual task management focus.
ClickUp offers a more comprehensive workspace hierarchy with docs, goals, and time tracking, while Todoist is simpler and faster for personal productivity with superior natural language date parsing.
OmniFocus is a macOS/iOS-only GTD task manager with no public REST API, making Todoist the clear choice when you need web-accessible programmatic task management in Bolt.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation