WhyTho

WhyTho API reference

Version 1.0.0. Base URL https://whytho-api.jakelabate.com

WhyTho records why the elements on a webpage are the way they are. A note is attached to a CSS selector rather than to a screenshot or a line number, and carries who wrote it, which viewport widths it applies to, and the context it was written in.

Authentication

Every request needs an API key in the Authorization header:

Authorization: Bearer why_live_...

Create one in the WhyTho app under API. A key is scoped to one workspace, personal or an organization, and is created either read only or read and write. A read only key is refused with 403 on anything that is not a GET. Keys are stored as hashes, so a lost key is replaced rather than recovered.

What a key can reach

Exactly what its owner could reach in the app, and nothing more. Only the author of a note can edit or delete it. Only organization admins can change teams, membership and invites. The owner of an organization cannot be removed through the API at all.

Provenance

Notes created through this API are stored with source: "api", because nothing verified that the selector was ever on the page. Notes captured by the annotator carry source: "annotator". Filter on it with ?source=annotator when the difference matters, which it does if you are using notes as a build guardrail.

Rate limits

120 requests a minute per key, counted at the Cloudflare edge, and 20 a minute per IP for anything without a key. Going over returns 429 with a Retry-After header. Cache what you poll, and page in blocks of 200 rather than looping single requests.

Built by Jake Labate.

Prefer to poke at it? The same spec is a Postman collection, or download the OpenAPI file.

Key

What the key can see.

What this key can see

GET/me

Confirms the key works and reports its workspace, role and scopes. Start here.

Example

curl "https://whytho-api.jakelabate.com/me" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Key details
401Missing, invalid or revoked key.

Notes

The reasoning itself.

Search notes

GET/notes

Every filter is optional and they combine into one query.

Parameters

NameTypeNotes
sitestringMatch part of the hostname. Example: example.com
pathstringPath prefix. Example: /products/
categoryseo or content or tech or a11y or perf or ux
statusdecided or proposed or question or do not change
applies_toall or mobile or tablet or laptop or desktopOnly notes that apply at this width.
sourceannotator or api
authorstringExact display name. Example: Jake Labate
teamstringExact team name.
qstringFree text inside the note body.
sincestringNotes created on or after this.
limitinteger
offsetinteger
orderoldestReverses the default newest first.

Example

curl "https://whytho-api.jakelabate.com/notes" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Matching notes
401Missing, invalid or revoked key.

Create a note

POST/notes

Write scope required. The page is created if it does not exist yet. An @name or @team in the body is resolved against your organization and delivered to those inboxes. Stored with source: "api".

Body

FieldTypeNotes
url requiredstring Example: https://example.com/pricing/
selector requiredstring Example: main > section.hero > h1
body requiredstring Example: Service plus city stays, the phrase carries local demand. @Engineering keep it out of an image.
categoryseo or content or tech or a11y or perf or ux
statusdecided or proposed or question or do not change
applies_toarray of all or mobile or tablet or laptop or desktop
page_titlestring
idstringYour own id for the note. Sending the same one again updates it rather than duplicating.

Example

curl -X POST "https://whytho-api.jakelabate.com/notes" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/pricing/",
  "selector": "main > section.hero > h1",
  "body": "Service plus city stays, the phrase carries local demand.",
  "category": "seo",
  "status": "do not change",
  "applies_to": [
    "all"
  ]
}'

Responses

201Created
400A note needs a full url, a selector and a body.
403Read only key.

Read one note

GET/notes/{id}

Parameters

NameTypeNotes
id requiredstring Example: n_mtvqtjrmx2lws

Example

curl "https://whytho-api.jakelabate.com/notes/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200The note
404No such note in this workspace.

Update your own note

PATCH/notes/{id}

Write scope required, and only the author of a note can change it.

Parameters

NameTypeNotes
id requiredstring Example: n_mtvqtjrmx2lws

Body

FieldTypeNotes
bodystring
selectorstring
categoryseo or content or tech or a11y or perf or ux
statusdecided or proposed or question or do not change
applies_toarray of string

Example

curl -X PATCH "https://whytho-api.jakelabate.com/notes/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "status": "decided",
  "body": "Shipped in release 2026.09. Do not revert without asking."
}'

Responses

200Updated
403Read only key, or not the author.
404No such note.

Delete your own note

DELETE/notes/{id}

Write scope required, and only the author of a note can delete it.

Parameters

NameTypeNotes
id requiredstring Example: n_mtvqtjrmx2lws

Example

curl -X DELETE "https://whytho-api.jakelabate.com/notes/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Deleted
403Read only key, or not the author.

Pages

Pages that carry notes.

List annotated pages

GET/pages

Every page carrying notes in this workspace, with a note count each.

Example

curl "https://whytho-api.jakelabate.com/pages" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Pages
401Missing, invalid or revoked key.

Rename a page

PATCH/pages/{id}

Write scope required.

Parameters

NameTypeNotes
id requiredstringPage id from /pages.

Body

FieldTypeNotes
title requiredstring

Example

curl -X PATCH "https://whytho-api.jakelabate.com/pages/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

200Updated
403Read only key.
404No such page in this workspace.

Delete a page and its notes

DELETE/pages/{id}

Write scope required. In an organization, admins only.

Parameters

NameTypeNotes
id requiredstringPage id from /pages.

Example

curl -X DELETE "https://whytho-api.jakelabate.com/pages/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Deleted
403Read only key, or not an admin.
404No such page.

Teams

Teams inside an organization.

List teams

GET/teams

Example

curl "https://whytho-api.jakelabate.com/teams" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Teams
400This key is personal, so it has no teams.

Create a team

POST/teams

Write scope required. Admins only.

Body

FieldTypeNotes
name requiredstring Example: Engineering

Example

curl -X POST "https://whytho-api.jakelabate.com/teams" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

201Created
403Read only key, or not an admin.

Rename a team

PATCH/teams/{id}

Write scope required. Admins only.

Parameters

NameTypeNotes
id requiredstring

Body

FieldTypeNotes
name requiredstring

Example

curl -X PATCH "https://whytho-api.jakelabate.com/teams/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

200Renamed
403Not an admin.

Delete a team

DELETE/teams/{id}

Write scope required. Admins only.

Parameters

NameTypeNotes
id requiredstring

Example

curl -X DELETE "https://whytho-api.jakelabate.com/teams/{id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Deleted
403Not an admin.

List team members

GET/teams/{id}/members

Parameters

NameTypeNotes
id requiredstring

Example

curl "https://whytho-api.jakelabate.com/teams/{id}/members" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Members

Add somebody to a team

POST/teams/{id}/members

Write scope required. Anyone can add themselves, admins can add others.

Parameters

NameTypeNotes
id requiredstring

Body

FieldTypeNotes
user_idstringOmit to add yourself.

Example

curl -X POST "https://whytho-api.jakelabate.com/teams/{id}/members" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

201Added
400That person is not in this organization.
403Not an admin.

Remove somebody from a team

DELETE/teams/{id}/members/{user_id}

Write scope required. Anyone can remove themselves, admins can remove others.

Parameters

NameTypeNotes
id requiredstring
user_id requiredstring

Example

curl -X DELETE "https://whytho-api.jakelabate.com/teams/{id}/members/{user_id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Removed
403Not an admin.

People

Membership and invites.

List everyone in the organization

GET/members

Example

curl "https://whytho-api.jakelabate.com/members" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Members
400This key is personal, so it has no members.

Change somebody's role

PATCH/members/{user_id}

Write scope required. Admins only, and never the owner.

Parameters

NameTypeNotes
user_id requiredstring

Body

FieldTypeNotes
role requiredadmin or member

Example

curl -X PATCH "https://whytho-api.jakelabate.com/members/{user_id}" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

200Changed
400You cannot change your own role.
403Not an admin.

Remove somebody from the organization

DELETE/members/{user_id}

Write scope required. Admins only. The owner cannot be removed.

Parameters

NameTypeNotes
user_id requiredstring

Example

curl -X DELETE "https://whytho-api.jakelabate.com/members/{user_id}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Removed
403Not an admin.

List invite codes

GET/invites

Admins only.

Example

curl "https://whytho-api.jakelabate.com/invites" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Invites

Mint an invite code

POST/invites

Write scope required. Admins only. Codes last 30 days. People join by entering it in the app.

Body

FieldTypeNotes
roleadmin or member
team_idstringOptional. Joining with this code also joins that team.

Example

curl -X POST "https://whytho-api.jakelabate.com/invites" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "role": "member"
}'

Responses

201Created
403Not an admin.

Revoke an invite code

DELETE/invites/{code}

Write scope required. Admins only.

Parameters

NameTypeNotes
code requiredstring

Example

curl -X DELETE "https://whytho-api.jakelabate.com/invites/{code}" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Revoked

Inbox

Notifications from @ mentions.

Your inbox

GET/notifications

Notes where you or one of your teams was tagged.

Parameters

NameTypeNotes
unreadbooleantrue for unread only.

Example

curl "https://whytho-api.jakelabate.com/notifications" \
  -H "Authorization: Bearer $WHYTHO_KEY"

Responses

200Notifications

Mark notifications read

POST/notifications/read

Write scope required. Send ids for specific ones, or nothing to mark everything read.

Body

FieldTypeNotes
idsarray of string

Example

curl -X POST "https://whytho-api.jakelabate.com/notifications/read" \
  -H "Authorization: Bearer $WHYTHO_KEY" \
  -H "Content-Type: application/json"

Responses

200Marked
403Read only key.

Objects

Note

FieldTypeNotes
idstringStable id for the note. Use it on /notes/{id}. Example: n_mtvqtjrmx2lws
urlstring Example: https://example.com/pricing/
originstring Example: https://example.com
pathstring Example: /pricing/
page_titlestring
selectorstringCSS selector for the element the note is about. Example: main > section.hero > h1
fallback_selectorstring
elementstring Example: h1
element_textstring Example: Same day plumbing repair
bodystring Example: Service plus city stays, the phrase carries local demand.
categoryseo or content or tech or a11y or perf or ux
statusdecided or proposed or question or do not change
applies_toarray of all or mobile or tablet or laptop or desktopWhich viewport widths the note is about, separate from the width it was taken at.
authorstringResolved from the account that wrote it. Never sent by a client.
teamstring
mentionsarray of objectResolved in the database from @names in the body, and delivered to those inboxes.
viewportobjectThe viewport the note was written at.
sourceannotator or apiannotator means a person selected a real element on a real page. api means it was asserted through this API and nothing verified it.
created_atstring
updated_atstring

Page

FieldTypeNotes
idstring
urlstring
originstring
pathstring
titlestring
notesinteger
updated_atstring

Team

FieldTypeNotes
idstring
namestring
membersinteger
created_atstring

Member

FieldTypeNotes
user_idstring
namestring
roleowner or admin or member
created_atstring

Error

FieldTypeNotes
errorstring