4todo Documentation

API status: unstable. This API can change without notice.

Use this API to manage your todos and recurring tasks.

Authentication

Send a Bearer token in the Authorization header.

Authorization: Bearer YOUR_API_TOKEN

Send Content-Type: application/json for requests with a body.

Create API tokens in the 4todo app settings. The app shows a token only once, when you create it. Store it securely.

Base URL

https://4to.do/api/v0

Quadrant System

The Eisenhower Matrix uses four quadrants.

CodeDescriptionMeaning
IUImportant & UrgentDo first - Critical tasks requiring immediate attention
INImportant & Not UrgentSchedule - Important tasks for long-term success
NUNot Important & UrgentDelegate - Tasks that need to be done but not by you
NNNot Important & Not UrgentEliminate - Low-value tasks to minimize

Workspaces

GET /api/v0/todos requires a workspace ID.

Workspace IDs use the ws_ prefix.

List workspaces with GET /api/v0/workspaces.

A restricted workspace is read-only. Mutations return 402 with code: WORKSPACE_RESTRICTED.

Endpoints

Workspaces

List Workspaces

List your workspaces.

GET /api/v0/workspaces

Example Request (curl):

curl -sS \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://4to.do/api/v0/workspaces"

Example Response:

[
  {
    "id": "ws_01hqk8z9w3r2n1p0m4v5x7y6",
    "name": "Default",
    "description": "Your default workspace",
    "icon": "",
    "is_default": true,
    "sort_order": "0|",
    "created_at": "2026-01-15T10:30:00Z",
    "updated_at": "2026-01-15T10:30:00Z"
  }
]

Todos

Get Todos

Get pending, non-deleted todos for one workspace.

GET /api/v0/todos

Query Parameters:

ParameterRequiredDescription
workspaceYesWorkspace ID (TypeID, ws_...)
showNoSet to all to include recurring todo configs as rec_todo_... pseudo IDs

Response:

The response is an object keyed by quadrant. Each value is an array of todo objects.

Todo fields:

FieldTypeDescription
idstringTodo ID (todo_...) or recurring todo ID (rec_todo_...)
namestringTodo title
quadrantstringIU, IN, NU, NN
completed_atstring|nullRFC 3339 timestamp, or null
typestringregular, recurring, or recurring_instance
recurring_todo_idstringPresent for recurring and recurring_instance
recurring_configobjectPresent only for recurring

Recurring config fields (recurring_config):

FieldTypeDescription
idstringRecurring todo ID (rec_todo_...)
titlestringRecurring todo title
quadrantstringIU, IN, NU, NN
frequencystringdaily, weekly, or monthly
weekdaysstring[]Use for weekly
month_daynumberUse for monthly
timezonestringIANA timezone
start_datestringRFC 3339 timestamp
end_datestring|nullRFC 3339 timestamp, or null
rrulestringRecurrence rule string used by the backend

Example Request:

GET /api/v0/todos?workspace=ws_01hqk8z9w3r2n1p0m4v5x7y6

Example Request (curl):

curl -sS \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://4to.do/api/v0/todos?workspace=ws_01hqk8z9w3r2n1p0m4v5x7y6"

Example Response:

{
  "IU": [
    {
      "id": "todo_01hqk8z9w3r2n1p0m4v5x7y6",
      "name": "Complete project proposal",
      "quadrant": "IU",
      "completed_at": null,
      "type": "regular"
    }
  ],
  "IN": [],
  "NU": [],
  "NN": []
}

Create Todo

Create a new todo item in a specific quadrant.

POST /api/v0/todos

Request Body:

FieldTypeRequiredDescription
namestringYesTodo item name
quadrantstringYesQuadrant type: IU, IN, NU, or NN
workspace_idstringNoWorkspace ID (TypeID, ws_...). Server uses your default workspace when you omit it.

You can also set the workspace query parameter. Use it when you cannot send workspace_id.

Example Request:

{
  "name": "Complete project proposal",
  "quadrant": "IU",
  "workspace_id": "ws_01hqk8z9w3r2n1p0m4v5x7y6"
}

Example Request (curl):

curl -sS -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Complete project proposal","quadrant":"IU","workspace_id":"ws_01hqk8z9w3r2n1p0m4v5x7y6"}' \
  "https://4to.do/api/v0/todos"

Example Response:

{
  "message": "Todo created successfully"
}

Complete Todo

Mark a todo item as completed.

POST /api/v0/todos/:id/complete

URL Parameters:

ParameterDescription
idTodo ID (TypeID format: todo_...)

Example Request (curl):

curl -sS -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://4to.do/api/v0/todos/todo_01hqk8z9w3r2n1p0m4v5x7y6/complete"

Example Response:

{
  "message": "Todo completed successfully"
}

This endpoint is idempotent.

Error Response (404):

{
  "error": "Todo not found"
}

Reorder Todos

Reorder todos within the same quadrant or move them between quadrants.

POST /api/v0/todos/reorder

Request Body:

FieldTypeRequiredDescription
moved_todo_idstringYesID of the todo being moved
previous_todo_idstringNoID of the todo before the new position (null if moving to first position)
next_todo_idstringNoID of the todo after the new position (null if moving to last position)
quadrantstringYesTarget quadrant: IU, IN, NU, or NN

If moved_todo_id starts with rec_todo_, the API updates the recurring todo quadrant. It ignores previous_todo_id and next_todo_id.

Example Request:

{
  "moved_todo_id": "todo_01hqk8z9w3r2n1p0m4v5x7y6",
  "previous_todo_id": "todo_01hqk8z8x2q1m0n3k4u5w6x7",
  "next_todo_id": null,
  "quadrant": "IN"
}

Example Request (curl):

curl -sS -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"moved_todo_id":"todo_01hqk8z9w3r2n1p0m4v5x7y6","previous_todo_id":"todo_01hqk8z8x2q1m0n3k4u5w6x7","next_todo_id":null,"quadrant":"IN"}' \
  "https://4to.do/api/v0/todos/reorder"

Example Response:

{
  "message": "Todo reordered successfully"
}

Error Response (400):

{
  "error": "Invalid quadrant type"
}

Error Response (404):

{
  "error": "Todo not found"
}

Recurring Todos

Recurring todos automatically create new instances based on a schedule.

Get Recurring Todos

List recurring todos.

GET /api/v0/recurring-todos

Query Parameters:

ParameterRequiredDescription
workspaceNoFilter by workspace (ws_...)

Example Request (curl):

curl -sS \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://4to.do/api/v0/recurring-todos?workspace=ws_01hqk8z9w3r2n1p0m4v5x7y6"

Example Response:

[
  {
    "id": "rec_todo_01hqk8z9w3r2n1p0m4v5x7y6",
    "title": "Morning standup",
    "quadrant": "IU",
    "frequency": "daily",
    "timezone": "America/Los_Angeles",
    "start_date": "2026-01-15T10:30:00Z",
    "end_date": null,
    "rrule": "FREQ=DAILY"
  }
]

Create Recurring Todo

Create a new recurring todo with a specified frequency.

POST /api/v0/recurring-todos

Query Parameters:

ParameterRequiredDescription
workspaceNoTarget workspace (ws_...). Server uses your default when you omit it.

Request Body:

FieldTypeRequiredDescription
titlestringYesRecurring todo title
quadrantstringYesQuadrant type: IU, IN, NU, or NN
frequencystringYesdaily, weekly, or monthly
weekdaysarrayNoRecommended for weekly frequency. Use weekday codes: ["MO", "TU", "WE", "TH", "FR", "SA", "SU"]
month_dayintegerConditionalRequired for monthly frequency. Day of month (1-31)
end_datestringNoEnd date in RFC 3339 format (example: "2026-12-31T00:00:00Z")
timezonestringNoIANA timezone identifier. Server uses UTC when you omit it.

Example Request (curl):

curl -sS -X POST \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Morning standup","quadrant":"IU","frequency":"daily","timezone":"America/Los_Angeles"}' \
  "https://4to.do/api/v0/recurring-todos?workspace=ws_01hqk8z9w3r2n1p0m4v5x7y6"

Example Request (Daily):

{
  "title": "Morning standup",
  "quadrant": "IU",
  "frequency": "daily",
  "timezone": "America/Los_Angeles"
}

Example Request (Weekly):

{
  "title": "Team meeting",
  "quadrant": "IN",
  "frequency": "weekly",
  "weekdays": ["MO", "WE", "FR"],
  "timezone": "America/New_York"
}

Example Request (Monthly):

{
  "title": "Monthly report",
  "quadrant": "IN",
  "frequency": "monthly",
  "month_day": 1,
  "timezone": "UTC"
}

Example Response:

{
  "message": "Recurring todo created successfully"
}

Get Recurring Todo

Retrieve details of a specific recurring todo.

GET /api/v0/recurring-todos/:id

URL Parameters:

ParameterDescription
idRecurring todo ID

Example Request (curl):

curl -sS \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  "https://4to.do/api/v0/recurring-todos/rec_todo_01hqk8z9w3r2n1p0m4v5x7y6"

Example Response:

{
  "id": "rec_todo_01hqk8z9w3r2n1p0m4v5x7y6",
  "title": "Morning standup",
  "quadrant": "IU",
  "frequency": "daily",
  "timezone": "America/Los_Angeles",
  "start_date": "2026-01-15T10:30:00Z",
  "end_date": null,
  "rrule": "FREQ=DAILY"
}

Update Recurring Todo

Update an existing recurring todo's properties.

PUT /api/v0/recurring-todos/:id

Request Body:

FieldTypeRequiredDescription
titlestringYesRecurring todo title
quadrantstringYesIU, IN, NU, NN
frequencystringYesdaily, weekly, or monthly
weekdaysarrayNoUse for weekly
month_dayintegerNoRequired for monthly
start_datestringNoRFC 3339 timestamp
end_datestringNoRFC 3339 timestamp
timezonestringYesIANA timezone

Example Request (curl):

curl -sS -X PUT \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"Morning standup","quadrant":"IU","frequency":"daily","timezone":"America/Los_Angeles"}' \
  "https://4to.do/api/v0/recurring-todos/rec_todo_01hqk8z9w3r2n1p0m4v5x7y6"

Example Response:

{
  "message": "Recurring todo updated successfully"
}

Delete Recurring Todo

Delete a recurring todo. This will stop future instances from being created.

DELETE /api/v0/recurring-todos/:id

Example Response:

{
  "message": "Recurring todo deleted successfully"
}

Error Responses

The API uses standard HTTP status codes to indicate success or failure.

Status Codes

CodeDescription
200Success - Request completed successfully
201Created - Resource created successfully
400Bad Request - Invalid request data or parameters
401Unauthorized - Missing or invalid authentication token
402Payment Required - Feature gated or workspace read-only
403Forbidden - Access denied
404Not Found - Requested resource does not exist
413Payload Too Large - Request body is too large
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Something went wrong on our end

Error Response Format

Most error responses use this structure. Some legacy endpoints return only error.

{
  "error": "error_code_or_message",
  "message": "Optional human-readable message"
}

Example Error:

{
  "error": "invalid_request",
  "message": "Invalid quadrant type"
}

Token Errors

Token auth errors use 401 and JSON responses.

Possible values for error:

Example: token expired

{
  "error": "token_expired",
  "message": "Your token has expired. Please create a new token in settings."
}

Rate Limiting

/api/v0 is rate-limited.

When you exceed the limit, the API returns 429. It includes Retry-After and X-RateLimit-* headers.

Example: rate limited

{
  "error": "rate_limited",
  "message": "Too many requests"
}

Feature Gate Errors

Feature gate errors use 402 and a structured JSON response.

Possible values for code:

{
  "code": "FEATURE_UNAVAILABLE",
  "message": "Feature 'recurring_tasks' is not available on your current plan",
  "feature_id": "recurring_tasks",
  "plan_id": "plan_..."
}

Best Practices

TypeID Format

All IDs in the API use TypeID format for time-sortable, globally unique identifiers:

Timezones

Always use valid IANA timezone identifiers. Common examples:

Recurrence Rules

The API exposes a backend rrule string. It uses a small subset of RFC 5545.

Pagination

Currently, API responses are not paginated. This may change in future versions.

Support

For API support or to report issues:


Last updated: 2026-01-28