API
The WristLog API lets you build your own integrations. For example, a home automation that logs a wristcheck when you put a watch on, or a dashboard that shows which watches need some wrist time. It exposes your collection (with wear statistics) and lets you create wristchecks.
Authentication
Every request must include your personal API token as a bearer token:
Authorization: Bearer YOUR_API_TOKEN
Find and copy your token in Settings → API. You can regenerate it there at any time. Regenerating immediately invalidates the old token, so update your integrations afterwards. Keep the token secret: anyone who has it can read your collection and log wristchecks.
Base URL
All endpoints live under:
https://app.wristlog.com/api/v1
Responses are JSON.
Timezones
Every date and time in the API uses the timezone configured in your account settings, not UTC and not your client's local time. This applies consistently across every endpoint, so it's worth understanding:
- Returned timestamps (like
last_worn_at) carry your timezone's offset, e.g.2026-06-14T08:30:00+02:00. - Day-based statistics (
worn_today,days_since_last_worn, and the rolling 7/30-day windows) are calculated against the current date in your timezone. - Logging a wristcheck with no
dateuses the current moment in your timezone. A date-only value like2026-06-14is interpreted as that calendar day in your timezone. - The one-per-day rule (below) also uses your local day boundaries, so "today" means your today.
If a wristcheck seems to land on the wrong day, check that your account timezone in settings matches where you actually are.
List your watches
GET /api/v1/watches returns every watch you have logged (active, archived and sold) with wear statistics. There are no query parameters: every watch comes back, so use the archived and sold fields to filter in your own code if you only want what you currently wear.
curl https://app.wristlog.com/api/v1/watches \
-H "Authorization: Bearer YOUR_API_TOKEN"
Example response:
{
"currency": "USD",
"watches": [
{
"id": 42,
"name": "Speedmaster",
"manufacturer": "Omega",
"model": "Moonwatch",
"reference_number": "310.30.42.50.01.001",
"movement_type": "Mechanical",
"color": "#3380cc",
"year": 2021,
"purchase_date": "2021-06-01",
"purchase_price": 6200.0,
"sold": false,
"sale_date": null,
"sale_price": null,
"image_url": "https://app.wristlog.com/rails/active_storage/representations/redirect/...",
"archived": false,
"total_wears": 128,
"last_worn_at": "2026-06-14T08:30:00+02:00",
"days_since_last_worn": 1,
"worn_today": false,
"wears_last_7_days": 3,
"wears_last_30_days": 11,
"service_cost": 740.0,
"total_cost": 6940.0,
"cost_per_wear": 54.22,
"counted_in_cost_per_wear": true,
"sale_result": null
}
]
}
Fields explained:
- image_url: link to the watch photo, or
nullif the watch has no photo. - total_wears: lifetime wristcheck count.
- last_worn_at: ISO 8601 timestamp of the most recent wristcheck, or
nullif never worn. - days_since_last_worn: whole days since last worn, or
nullif never worn. - worn_today: whether the watch already has a wristcheck logged today.
- wears_last_7_days / wears_last_30_days: wristchecks within those rolling windows.
- sold: whether the watch has left your collection. Archiving does not make a watch sold.
- sale_date: the day you sold it, or
nullif you still own it. - purchase_price / sale_price: what you paid and what you sold it for, or
nullif not set.
Money fields
Every watch list response carries a top-level currency with your account's ISO code, and every watch carries purchase_price and sale_price. If you also turned on money features in Settings, each watch gains the calculated figures below. While money features are off, those five keys are not in the response at all.
Amounts are plain numbers rounded to two decimals, with no currency symbol, so format them yourself using currency.
- service_cost: the sum of the costs on the watch's service records, or
nullif none are recorded. Records logged in a different currency are left out. - total_cost: purchase price plus service cost plus the other costs logged in the watch's Papers tab (straps, batteries).
nullwithout a purchase price, since the total is unknown then rather than just the service spend. - cost_per_wear: total cost minus the sale price, divided by the lifetime wear count. It's
nullwithout a purchase price or with no wears, and it goes negative when the sale covered more than the watch cost you. Alsonullfor a watch you left out of cost per wear. - counted_in_cost_per_wear:
falsewhen you left the watch out of cost per wear, from its Ownership details or the Money stats. - sale_result: sale price minus purchase price, so negative is a loss.
nullunless both prices are set. Service and other costs stay out of this one, they land incost_per_wear.
Log a wristcheck
POST /api/v1/watches/:watch_id/wristchecks records that you wore a watch. By default the wristcheck is logged for the current moment (in your account's timezone).
curl -X POST https://app.wristlog.com/api/v1/watches/42/wristchecks \
-H "Authorization: Bearer YOUR_API_TOKEN"
To backfill a different day, add an optional date (any value parseable as a date or datetime):
curl -X POST https://app.wristlog.com/api/v1/watches/42/wristchecks \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d "date=2026-06-14"
You can also tag the wear with optional occasions (free text, max 40 characters each, as many as you like). They feed the occasion charts on your stats page. A name you don't have yet is added to your occasion list in Settings, so send the names you actually use:
curl -X POST https://app.wristlog.com/api/v1/watches/42/wristchecks \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d "occasions[]=Travel" \
-d "occasions[]=Formal"
The older single-valued occasion=Travel still works and is treated as one tag. A successful request returns 201 Created. occasions comes back in the order of your occasion list in Settings, not the order you sent, and is an empty array when the wear isn't tagged:
{ "id": 5012, "watch_id": 42, "log_date": "2026-06-14T00:00:00+02:00", "occasions": ["Formal", "Travel"] }
You can log only one wristcheck per watch per day, and you can't log a wristcheck in the future. Either case returns 422 with an explanation.
Errors & rate limits
Errors return a JSON body. Authentication and validation failures look like:
{ "error": "Invalid API token" }
{ "errors": ["You already Wristchecked this watch today"] }
- 401 Unauthorized: missing or invalid token.
- 404 Not Found: the watch doesn't exist in your collection.
- 422 Unprocessable: validation failed (duplicate day, future date, unparseable date).
- 429 Too Many Requests: you've exceeded the rate limit.
The API is limited to 60 requests per minute per token. That's plenty for personal automations, so design yours to poll on a sensible interval rather than in a tight loop.