ticktick

package module
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 24, 2026 License: MIT Imports: 10 Imported by: 0

README

go-ticktick

CI codecov Go Report Card Go Reference Release License

Go client library for the TickTick Open API.

Installation

go get github.com/slavkluev/go-ticktick

Limitations

This library uses only the official TickTick Open API (V1). The unofficial V2 web API is not supported.

The V1 API does not provide access to:

  • Inbox — not exposed as a project, so tasks in it cannot be read or listed
  • Tags
  • Habits and focus tracking
  • Folders
  • Completed tasks
  • Filters / Smart lists

Authentication

The library requires an OAuth2 access token. TickTick uses the Authorization Code flow.

1. Register your application
  1. Open the TickTick Developer Center and sign in.
  2. Click New App, fill in Name (e.g. "My App") and click Add.
  3. Click Edit on your app to open App Setting.
  4. Copy your Client ID and Client Secret.
  5. Set the OAuth redirect URL to a URL your app will listen on (e.g. http://localhost:8080/callback) and click Save.
2. Redirect user to authorization page

Build the authorization URL and redirect the user:

https://ticktick.com/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=tasks:write%20tasks:read&redirect_uri=YOUR_REDIRECT_URI&response_type=code
Parameter Description
client_id Your application ID
scope tasks:read for read access, tasks:write for write access (both cover tasks and projects)
state Any value, passed back to redirect URL as-is (optional)
redirect_uri Your registered redirect URL
response_type Must be code
3. Receive the authorization code

After the user grants access, TickTick redirects to your redirect_uri with query parameters:

Parameter Description
code Authorization code to exchange for a token
state The same state value from step 2 (if provided)
4. Exchange code for access token

Exchange the authorization code for an access token:

curl -X POST https://ticktick.com/oauth/token \
  -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
  -d "code=AUTHORIZATION_CODE&grant_type=authorization_code&scope=tasks:write%20tasks:read&redirect_uri=YOUR_REDIRECT_URI"

The response JSON contains the access_token:

{
  "access_token": "your-access-token",
  "token_type": "bearer",
  "expires_in": 15551999,
  "scope": "tasks:read tasks:write"
}
5. Use the token

Pass the token when creating the client:

client := ticktick.NewClient("your-access-token")

Usage

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/slavkluev/go-ticktick"
)

func main() {
	client := ticktick.NewClient("your-access-token")
	ctx := context.Background()

	// List all projects
	projects, err := client.GetProjects(ctx)
	if err != nil {
		log.Fatal(err)
	}
	for _, p := range projects {
		fmt.Printf("Project: %s (%s)\n", p.Name, p.ID)
	}

	// Create a task
	task, err := client.CreateTask(ctx, &ticktick.CreateTaskRequest{
		Title:     "Buy groceries",
		ProjectID: projects[0].ID,
		Priority:  ticktick.Int(ticktick.PriorityMedium),
		IsAllDay:  ticktick.Bool(true),
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Created task: %s\n", task.ID)

	// Complete the task
	err = client.CompleteTask(ctx, task.ProjectID, task.ID)
	if err != nil {
		log.Fatal(err)
	}
}

API

Client
client := ticktick.NewClient("access-token")

// Customize HTTP client or base URL
client = ticktick.NewClient("access-token",
	ticktick.WithHTTPClient(&http.Client{Timeout: 10 * time.Second}),
	ticktick.WithBaseURL("https://api.dida365.com"),
)
Tasks
Method Description
GetTask(ctx, projectID, taskID) Get a task by project and task ID
CreateTask(ctx, *CreateTaskRequest) Create a new task
UpdateTask(ctx, taskID, *UpdateTaskRequest) Update an existing task
CompleteTask(ctx, projectID, taskID) Mark a task as complete
DeleteTask(ctx, projectID, taskID) Delete a task
Projects
Method Description
GetProjects(ctx) List all projects
GetProject(ctx, projectID) Get a project by ID
GetProjectData(ctx, projectID) Get a project with its tasks and columns
CreateProject(ctx, *CreateProjectRequest) Create a new project
UpdateProject(ctx, projectID, *UpdateProjectRequest) Update an existing project
DeleteProject(ctx, projectID) Delete a project
Error Handling

API errors are returned as *ticktick.Error with the HTTP status code and response body:

import "errors"

task, err := client.GetTask(ctx, "proj1", "task1")
if err != nil {
	var apiErr *ticktick.Error
	if errors.As(err, &apiErr) {
		fmt.Printf("HTTP %d: %s\n", apiErr.StatusCode, apiErr.Body)
	}
}
Constants

The library provides constants for common field values:

// Task priority
ticktick.PriorityNone      // 0
ticktick.PriorityLow       // 1
ticktick.PriorityMedium    // 3
ticktick.PriorityHigh      // 5

// Task status
ticktick.TaskStatusNormal       // 0
ticktick.TaskStatusCompleted    // 2

// Checklist item status
ticktick.ChecklistStatusNormal       // 0
ticktick.ChecklistStatusCompleted    // 1

// Project view mode
ticktick.ViewModeList       // "list"
ticktick.ViewModeKanban     // "kanban"
ticktick.ViewModeTimeline   // "timeline"

// Project kind
ticktick.ProjectKindTask    // "TASK"
ticktick.ProjectKindNote    // "NOTE"

// Task kind
ticktick.TaskKindText       // "TEXT"
ticktick.TaskKindNote       // "NOTE"
ticktick.TaskKindChecklist  // "CHECKLIST"

// Project permission
ticktick.PermissionRead     // "read"
ticktick.PermissionWrite    // "write"
ticktick.PermissionComment  // "comment"

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package ticktick provides a Go client for the TickTick Open API.

Usage

import "github.com/slavkluev/go-ticktick"

Construct a new client with an OAuth2 access token, then call methods to access the TickTick API. For example:

client := ticktick.NewClient("your-access-token")

// List all projects.
projects, err := client.GetProjects(ctx)

// Create a high-priority task.
task, err := client.CreateTask(ctx, &ticktick.CreateTaskRequest{
	Title:     "Buy groceries",
	ProjectID: projects[0].ID,
	Priority:  ticktick.Int(ticktick.PriorityHigh),
})

// Mark it complete.
err = client.CompleteTask(ctx, task.ProjectID, task.ID)

The client supports customization through functional options:

client := ticktick.NewClient("your-access-token",
	ticktick.WithHTTPClient(httpClient),
	ticktick.WithBaseURL("https://api.dida365.com"),
)

All methods accept a context.Context as the first parameter for cancellation and timeouts.

Authentication

The TickTick API uses OAuth2 Bearer tokens. Obtain an access token through the Authorization Code flow described in the TickTick Developer Center at https://developer.ticktick.com/api, then pass it to NewClient.

Creating and Updating Resources

Request types use pointer fields for optional values, allowing the API to distinguish between unset fields and zero values. Helper functions String, Int, Int64, Bool, and NewTime create the required pointers:

req := &ticktick.CreateTaskRequest{
	Title:     "Weekly report",
	ProjectID: "project-id",
	Content:   ticktick.String("Status update"),
	Priority:  ticktick.Int(ticktick.PriorityMedium),
	DueDate:   ticktick.NewTime(time.Now().Add(24 * time.Hour)),
}

Error Handling

API errors are returned as *Error with the HTTP status code and response body. Use errors.As to inspect them:

task, err := client.GetTask(ctx, projectID, taskID)
if err != nil {
	var apiErr *ticktick.Error
	if errors.As(err, &apiErr) {
		log.Printf("HTTP %d: %s", apiErr.StatusCode, apiErr.Body)
	}
}

Index

Examples

Constants

View Source
const (
	PriorityNone   = 0
	PriorityLow    = 1
	PriorityMedium = 3
	PriorityHigh   = 5
)

Task priority levels.

View Source
const (
	TaskStatusNormal    = 0
	TaskStatusCompleted = 2
)

Task completion status values.

View Source
const (
	ChecklistStatusNormal    = 0
	ChecklistStatusCompleted = 1
)

ChecklistItem completion status values.

View Source
const (
	ViewModeList     = "list"
	ViewModeKanban   = "kanban"
	ViewModeTimeline = "timeline"
)

Project view modes.

View Source
const (
	ProjectKindTask = "TASK"
	ProjectKindNote = "NOTE"
)

Project kinds.

View Source
const (
	TaskKindText      = "TEXT"
	TaskKindNote      = "NOTE"
	TaskKindChecklist = "CHECKLIST"
)

Task kinds.

View Source
const (
	PermissionRead    = "read"
	PermissionWrite   = "write"
	PermissionComment = "comment"
)

Project permission levels.

View Source
const DefaultBaseURL = "https://api.ticktick.com"

DefaultBaseURL is the default base URL of the TickTick API.

Variables

This section is empty.

Functions

func Bool

func Bool(v bool) *bool

Bool returns a pointer to the given bool value.

func Int

func Int(v int) *int

Int returns a pointer to the given int value.

func Int64

func Int64(v int64) *int64

Int64 returns a pointer to the given int64 value.

func String

func String(v string) *string

String returns a pointer to the given string value.

Types

type ChecklistItem

type ChecklistItem struct {
	ID            string `json:"id"`
	Title         string `json:"title"`
	Status        int    `json:"status"`
	CompletedTime Time   `json:"completedTime"`
	IsAllDay      bool   `json:"isAllDay"`
	SortOrder     int64  `json:"sortOrder"`
	StartDate     Time   `json:"startDate"`
	TimeZone      string `json:"timeZone"`
}

ChecklistItem represents a subtask within a task.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client manages communication with the TickTick Open API.

func NewClient

func NewClient(accessToken string, opts ...Option) *Client

NewClient creates a new TickTick API client with the given access token.

Example
client := ticktick.NewClient("your-access-token")

projects, err := client.GetProjects(context.Background())
if err != nil {
	// handle error
	return
}

for _, p := range projects {
	fmt.Println(p.Name)
}
Example (WithOptions)
client := ticktick.NewClient("your-access-token",
	ticktick.WithHTTPClient(&http.Client{
		Timeout: 10 * time.Second,
	}),
	// Use Dida365 API (TickTick's Chinese version).
	ticktick.WithBaseURL("https://api.dida365.com"),
)

projects, err := client.GetProjects(context.Background())
if err != nil {
	// handle error
	return
}

for _, p := range projects {
	fmt.Println(p.Name)
}

func (*Client) CompleteTask

func (c *Client) CompleteTask(ctx context.Context, projectID, taskID string) error

CompleteTask marks a task as complete.

func (*Client) CreateProject

func (c *Client) CreateProject(ctx context.Context, req *CreateProjectRequest) (*Project, error)

CreateProject creates a new project.

Example
client := ticktick.NewClient("your-access-token")

project, err := client.CreateProject(context.Background(), &ticktick.CreateProjectRequest{
	Name:     "Work Tasks",
	Color:    ticktick.String("#F18181"),
	ViewMode: ticktick.String(ticktick.ViewModeList),
	Kind:     ticktick.String(ticktick.ProjectKindTask),
})
if err != nil {
	// handle error
	return
}

fmt.Println(project.ID)

func (*Client) CreateTask

func (c *Client) CreateTask(ctx context.Context, req *CreateTaskRequest) (*Task, error)

CreateTask creates a new task.

Example
client := ticktick.NewClient("your-access-token")

task, err := client.CreateTask(context.Background(), &ticktick.CreateTaskRequest{
	Title:     "Buy groceries",
	ProjectID: "project-id",
	Priority:  ticktick.Int(ticktick.PriorityHigh),
})
if err != nil {
	// handle error
	return
}

fmt.Println(task.ID)
Example (WithDetails)
client := ticktick.NewClient("your-access-token")

task, err := client.CreateTask(context.Background(), &ticktick.CreateTaskRequest{
	Title:     "Weekly report",
	ProjectID: "project-id",
	Content:   ticktick.String("Prepare and send the weekly status report"),
	Desc:      ticktick.String("Include metrics from dashboard"),
	IsAllDay:  ticktick.Bool(false),
	StartDate: ticktick.NewTime(time.Date(2024, 1, 15, 9, 0, 0, 0, time.UTC)),
	DueDate:   ticktick.NewTime(time.Date(2024, 1, 15, 17, 0, 0, 0, time.UTC)),
	TimeZone:  ticktick.String("America/New_York"),
	// Reminders use iCalendar TRIGGER format.
	// "TRIGGER:PT0S" — at the time of the event.
	// "TRIGGER:P0DT9H0M0S" — 9 hours before.
	// "TRIGGER:-PT30M" — 30 minutes before.
	Reminders: []string{"TRIGGER:PT0S", "TRIGGER:-PT30M"},
	// RepeatFlag uses iCalendar RRULE format.
	// "RRULE:FREQ=DAILY;INTERVAL=1" — every day.
	// "RRULE:FREQ=WEEKLY;INTERVAL=2" — every 2 weeks.
	// "RRULE:FREQ=MONTHLY;INTERVAL=1;BYDAY=1MO" — first Monday of each month.
	RepeatFlag: ticktick.String("RRULE:FREQ=WEEKLY;INTERVAL=1"),
	Priority:   ticktick.Int(ticktick.PriorityMedium),
	SortOrder:  ticktick.Int64(100),
	Items: []ticktick.CreateChecklistItemRequest{
		{Title: "Gather metrics"},
		{Title: "Write summary"},
		{Title: "Send to team"},
	},
})
if err != nil {
	// handle error
	return
}

fmt.Println(task.ID)

func (*Client) DeleteProject

func (c *Client) DeleteProject(ctx context.Context, projectID string) error

DeleteProject deletes a project.

func (*Client) DeleteTask

func (c *Client) DeleteTask(ctx context.Context, projectID, taskID string) error

DeleteTask deletes a task.

func (*Client) GetProject

func (c *Client) GetProject(ctx context.Context, projectID string) (*Project, error)

GetProject retrieves a project by ID.

func (*Client) GetProjectData

func (c *Client) GetProjectData(ctx context.Context, projectID string) (*ProjectData, error)

GetProjectData retrieves a project along with its tasks and columns.

Example
client := ticktick.NewClient("your-access-token")

data, err := client.GetProjectData(context.Background(), "project-id")
if err != nil {
	// handle error
	return
}

fmt.Printf("Project: %s\n", data.Project.Name)

for _, task := range data.Tasks {
	fmt.Printf("  Task: %s (priority=%d)\n", task.Title, task.Priority)
}

for _, col := range data.Columns {
	fmt.Printf("  Column: %s\n", col.Name)
}

func (*Client) GetProjects

func (c *Client) GetProjects(ctx context.Context) ([]Project, error)

GetProjects returns all projects for the authenticated user.

func (*Client) GetTask

func (c *Client) GetTask(ctx context.Context, projectID, taskID string) (*Task, error)

GetTask retrieves a task by project ID and task ID.

Example (ErrorHandling)
client := ticktick.NewClient("your-access-token")

task, err := client.GetTask(context.Background(), "project-id", "task-id")
if err != nil {
	var apiErr *ticktick.Error
	if errors.As(err, &apiErr) {
		fmt.Printf("API error: HTTP %d: %s\n", apiErr.StatusCode, apiErr.Body)

		return
	}

	// Network or other error.
	fmt.Printf("unexpected error: %v\n", err)

	return
}

fmt.Println(task.Title)

func (*Client) UpdateProject

func (c *Client) UpdateProject(ctx context.Context, projectID string, req *UpdateProjectRequest) (*Project, error)

UpdateProject updates an existing project.

func (*Client) UpdateTask

func (c *Client) UpdateTask(ctx context.Context, taskID string, req *UpdateTaskRequest) (*Task, error)

UpdateTask updates an existing task.

Example
client := ticktick.NewClient("your-access-token")

task, err := client.UpdateTask(context.Background(), "task-id", &ticktick.UpdateTaskRequest{
	ID:        "task-id",
	ProjectID: "project-id",
	Title:     ticktick.String("Updated title"),
	Priority:  ticktick.Int(ticktick.PriorityLow),
	DueDate:   ticktick.NewTime(time.Date(2024, 2, 1, 12, 0, 0, 0, time.UTC)),
})
if err != nil {
	// handle error
	return
}

fmt.Println(task.Title)

type Column

type Column struct {
	ID        string `json:"id"`
	ProjectID string `json:"projectId"`
	Name      string `json:"name"`
	SortOrder int64  `json:"sortOrder"`
}

Column represents a kanban column within a project.

type CreateChecklistItemRequest

type CreateChecklistItemRequest struct {
	Title         string  `json:"title"`
	StartDate     *Time   `json:"startDate,omitempty"`
	IsAllDay      *bool   `json:"isAllDay,omitempty"`
	SortOrder     *int64  `json:"sortOrder,omitempty"`
	TimeZone      *string `json:"timeZone,omitempty"`
	Status        *int    `json:"status,omitempty"`
	CompletedTime *Time   `json:"completedTime,omitempty"`
}

CreateChecklistItemRequest contains the fields for a subtask in a create or update request.

type CreateProjectRequest

type CreateProjectRequest struct {
	Name      string  `json:"name"`
	Color     *string `json:"color,omitempty"`
	SortOrder *int64  `json:"sortOrder,omitempty"`
	ViewMode  *string `json:"viewMode,omitempty"`
	Kind      *string `json:"kind,omitempty"`
}

CreateProjectRequest contains the fields for creating a new project.

type CreateTaskRequest

type CreateTaskRequest struct {
	Title      string                       `json:"title"`
	ProjectID  string                       `json:"projectId"`
	Content    *string                      `json:"content,omitempty"`
	Desc       *string                      `json:"desc,omitempty"`
	IsAllDay   *bool                        `json:"isAllDay,omitempty"`
	StartDate  *Time                        `json:"startDate,omitempty"`
	DueDate    *Time                        `json:"dueDate,omitempty"`
	TimeZone   *string                      `json:"timeZone,omitempty"`
	Reminders  []string                     `json:"reminders,omitzero"`
	RepeatFlag *string                      `json:"repeatFlag,omitempty"`
	Priority   *int                         `json:"priority,omitempty"`
	SortOrder  *int64                       `json:"sortOrder,omitempty"`
	Items      []CreateChecklistItemRequest `json:"items,omitzero"`
}

CreateTaskRequest contains the fields for creating a new task.

type Error

type Error struct {
	StatusCode int
	Body       string
}

Error represents an error response from the TickTick API.

func (*Error) Error

func (e *Error) Error() string

type Option

type Option func(*Client)

Option configures a Client.

func WithBaseURL

func WithBaseURL(baseURL string) Option

WithBaseURL sets a custom base URL.

func WithHTTPClient

func WithHTTPClient(httpClient *http.Client) Option

WithHTTPClient sets a custom HTTP client.

type Project

type Project struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	Color      string `json:"color"`
	SortOrder  int64  `json:"sortOrder"`
	Closed     bool   `json:"closed"`
	GroupID    string `json:"groupId"`
	ViewMode   string `json:"viewMode"`
	Permission string `json:"permission"`
	Kind       string `json:"kind"`
}

Project represents a TickTick project.

type ProjectData

type ProjectData struct {
	Project Project  `json:"project"`
	Tasks   []Task   `json:"tasks"`
	Columns []Column `json:"columns"`
}

ProjectData holds a project along with its tasks and columns.

type Task

type Task struct {
	ID            string          `json:"id"`
	ProjectID     string          `json:"projectId"`
	Title         string          `json:"title"`
	IsAllDay      bool            `json:"isAllDay"`
	CompletedTime Time            `json:"completedTime"`
	Content       string          `json:"content"`
	Desc          string          `json:"desc"`
	DueDate       Time            `json:"dueDate"`
	Items         []ChecklistItem `json:"items"`
	Priority      int             `json:"priority"`
	Reminders     []string        `json:"reminders"`
	RepeatFlag    string          `json:"repeatFlag"`
	SortOrder     int64           `json:"sortOrder"`
	StartDate     Time            `json:"startDate"`
	Status        int             `json:"status"`
	TimeZone      string          `json:"timeZone"`
	Kind          string          `json:"kind"`
}

Task represents a TickTick task.

type Time

type Time struct {
	time.Time
}

Time wraps time.Time with custom JSON marshaling for the TickTick API date format.

func NewTime

func NewTime(t time.Time) *Time

NewTime creates a pointer to a Time value. Useful for optional date fields in request types.

func (Time) MarshalJSON

func (t Time) MarshalJSON() ([]byte, error)

MarshalJSON serializes a Time to JSON using the TickTick date format. A zero Time marshals to an empty string.

func (*Time) UnmarshalJSON

func (t *Time) UnmarshalJSON(data []byte) error

UnmarshalJSON deserializes a Time from JSON. It handles the standard TickTick date string format, date strings with fractional seconds, Unix millisecond timestamps (used in some checklist item fields), empty strings, and null values.

type UpdateProjectRequest

type UpdateProjectRequest struct {
	Name      *string `json:"name,omitempty"`
	Color     *string `json:"color,omitempty"`
	SortOrder *int64  `json:"sortOrder,omitempty"`
	ViewMode  *string `json:"viewMode,omitempty"`
	Kind      *string `json:"kind,omitempty"`
}

UpdateProjectRequest contains the fields for updating an existing project.

type UpdateTaskRequest

type UpdateTaskRequest struct {
	ID         string                       `json:"id"`
	ProjectID  string                       `json:"projectId"`
	Title      *string                      `json:"title,omitempty"`
	Content    *string                      `json:"content,omitempty"`
	Desc       *string                      `json:"desc,omitempty"`
	IsAllDay   *bool                        `json:"isAllDay,omitempty"`
	StartDate  *Time                        `json:"startDate,omitempty"`
	DueDate    *Time                        `json:"dueDate,omitempty"`
	TimeZone   *string                      `json:"timeZone,omitempty"`
	Reminders  []string                     `json:"reminders,omitzero"`
	RepeatFlag *string                      `json:"repeatFlag,omitempty"`
	Priority   *int                         `json:"priority,omitempty"`
	SortOrder  *int64                       `json:"sortOrder,omitempty"`
	Items      []CreateChecklistItemRequest `json:"items,omitzero"`
}

UpdateTaskRequest contains the fields for updating an existing task.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL