Skip to content

Commit 23f4ab5

Browse files
author
Matt Petters
committed
fix(auth): resolve V2 API 429 rate limit during authentication
The V2 API signon endpoint now requires additional headers to avoid 429 rate limiting at the AWS ELB level: - Add Origin: https://ticktick.com header - Add Referer: https://ticktick.com/ header - Update X-Device header to use full web app format (os, device, channel, etc.) The version number in X-Device is NOT strictly validated - the original 6430 works fine. The key fix is the Origin/Referer headers and the full X-Device structure. Fixes #33
1 parent 653a66f commit 23f4ab5

1 file changed

Lines changed: 32 additions & 13 deletions

File tree

src/ticktick_sdk/api/v2/auth.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,18 @@ class SessionHandler:
124124
cookies = session.cookies
125125
"""
126126

127-
# Minimal headers that work (based on pyticktick)
128-
# Keep it simple - don't over-engineer with browser-exact headers
129-
DEFAULT_USER_AGENT = "Mozilla/5.0 (rv:145.0) Firefox/145.0"
127+
# Headers must match what the web app sends to avoid 429 rate limiting
128+
# at the AWS ELB level. Origin and Referer headers are required.
129+
# Updated Feb 2026 to fix 429 errors - see GitHub issue #33
130+
DEFAULT_USER_AGENT = (
131+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
132+
"AppleWebKit/537.36 (KHTML, like Gecko) "
133+
"Chrome/120.0.0.0 Safari/537.36"
134+
)
135+
136+
# Web app version for X-Device header. The specific version number
137+
# doesn't appear to be validated, but the full X-Device format is required.
138+
WEB_APP_VERSION = 6430
130139

131140
def __init__(
132141
self,
@@ -171,26 +180,36 @@ def inbox_id(self) -> str | None:
171180
def _get_x_device_header(self) -> str:
172181
"""Get the x-device header JSON string.
173182
174-
Uses minimal format that works (based on pyticktick).
175-
Only 3 fields: platform, version, id
183+
Must use full web app format to avoid 429 rate limiting.
184+
The version number is not strictly validated, but the full
185+
structure with os/device/channel fields is required.
176186
"""
177187
import json
178188

179-
return json.dumps({
180-
"platform": "web",
181-
"version": 6430,
182-
"id": self.device_id,
183-
})
189+
return json.dumps(
190+
{
191+
"platform": "web",
192+
"os": "macOS 10.15.7",
193+
"device": "Chrome 120.0.0.0",
194+
"name": "",
195+
"version": self.WEB_APP_VERSION,
196+
"id": self.device_id,
197+
"channel": "website",
198+
"campaign": "",
199+
"websocket": "",
200+
}
201+
)
184202

185203
def _get_headers(self) -> dict[str, str]:
186204
"""Get headers for authentication requests.
187205
188-
Minimal headers - don't over-engineer with browser-exact headers.
189-
The API just needs User-Agent and X-Device.
190-
Content-Type is added automatically by httpx when using json=.
206+
Must include Origin and Referer headers to avoid 429 rate limiting
207+
at the AWS ELB level. Updated Feb 2026 - see GitHub issue #33.
191208
"""
192209
return {
193210
"User-Agent": self.DEFAULT_USER_AGENT,
211+
"Origin": "https://ticktick.com",
212+
"Referer": "https://ticktick.com/",
194213
"X-Device": self._get_x_device_header(),
195214
}
196215

0 commit comments

Comments
 (0)