Skip to content

Watches

A watch is a tracked entity — a trademark, a domain (v2), a business name (v2), … — paired with the upstream sources to scan against. The data model is intentionally polymorphic; see the internal schema notes for the full rationale.

The Watch resource

{
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": {
"niceClasses": [25, 35],
"designations": ["US", "FR"]
},
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-30T08:14:01.000Z"
}
FieldTypeNotes
idstringServer-assigned cuid(). Opaque.
watchType"trademark"MVP only ships trademark. v2 adds domain, business_name, social_handle, app_listing.
namestringThe brand / domain / handle being tracked.
attributesobjectType-specific shape, owned by the source adapter. For trademark: { niceClasses: number[], designations: string[] } (ISO 3166 alpha-2).
sourcesstring[]Upstream providers. MVP only ships global_trademark_database. v2 adds uspto, euipo, whois, dns, companies_house, app_store, play_store.
scanCadence"hourly" | "daily" | "weekly"Subject to your tier’s minimum cadence (see plan tiers).
activebooleanfalse after a DELETE (soft-delete).
logoUrl, logoSt13string | nullTrademark-only convenience fields. Always null for non-trademark watches.
createdAtstringISO-8601.

GET /api/v1/watches

List watches owned by the calling user (or the user’s organization, when applicable).

Query parameters

ParameterTypeDefaultNotes
limitinteger50Max 200. See pagination.
cursorstringOpaque cursor from a previous response.
watchTypestringFilter to one watch type.
activebooleanFilter by active flag. Pass false to see soft-deleted watches.

Required scope: watches:read.

Example request

Terminal window
curl -sS https://api.trademarksentinel.app/api/v1/watches?limit=2 \
-H "Authorization: Bearer ts_REPLACE_ME"

Example response — 200 OK

{
"data": [
{
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-30T08:14:01.000Z"
},
{
"id": "ckxyz0000000ghijkl",
"watchType": "trademark",
"name": "Globex",
"attributes": { "niceClasses": [9, 42], "designations": ["US", "EP"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-29T17:02:55.000Z"
}
],
"pagination": {
"nextCursor": "ckxyz0000000ghijkl",
"limit": 2
}
}

Status codes

CodeWhen
200Success.
401Missing or invalid API key.
403Tier below Team, or scope watches:read missing.
429Rate limit exceeded.

POST /api/v1/watches

Create a watch.

Required scope: watches:write.

Request body

{
"watchType": "trademark",
"name": "Acme",
"attributes": {
"niceClasses": [25],
"designations": ["US", "FR"]
},
"sources": ["global_trademark_database"],
"scanCadence": "daily"
}
FieldRequiredNotes
watchTypeyesMVP: "trademark".
nameyes1–200 chars.
attributesyesShape depends on watchType. For trademark: niceClasses (int array, 1–45, 1–45 entries) and designations (ISO 3166 alpha-2 array, ≥ 1 entry).
sourcesn/aServer-assigned; the canonical sources for watchType are used (["global_trademark_database"] for trademark in MVP). Client-supplied values are ignored.
scanCadencenoDefaults to daily. Subject to your tier’s minimum cadence (Plan §8); a faster cadence than your tier allows returns 402 with an upgradeUrl.

active and createdAt are server-assigned. logoUrl / logoSt13 are populated by the source adapter on subsequent scans.

Example request

Terminal window
curl -sS -X POST https://api.trademarksentinel.app/api/v1/watches \
-H "Authorization: Bearer ts_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US", "FR"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily"
}'

Example response — 201 Created

{
"data": {
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US", "FR"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-05-02T11:00:00.000Z"
}
}

Status codes

CodeWhen
201Watch created.
400Malformed JSON.
401Missing or invalid API key.
402Quota exceeded for your tier (e.g. maxWatches reached, or scanCadence faster than tier minimum). Body includes error.upgradeUrl: "/pricing" so the dashboard can deep-link.
403Tier below Team, or scope watches:write missing.
422Validation failed (unknown watchType, missing attributes.niceClasses, …). The error.details carries fieldErrors from the server-side schema.
429Rate limit exceeded.

GET /api/v1/watches/{id}

Read a single watch.

Required scope: watches:read.

Example request

Terminal window
curl -sS https://api.trademarksentinel.app/api/v1/watches/ckxyz0000000abcdef \
-H "Authorization: Bearer ts_REPLACE_ME"

Example response — 200 OK

{
"data": {
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US", "FR"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-30T08:14:01.000Z"
}
}

Status codes

CodeWhen
200Success.
401Missing or invalid API key.
403Tier below Team, or scope watches:read missing.
404The watch does not exist or is not visible to the calling key.
429Rate limit exceeded.

PATCH /api/v1/watches/{id}

Partial update. Send only the fields you want to change.

Required scope: watches:write.

Request body

Any subset of:

{
"name": "Acme Renamed",
"attributes": { "niceClasses": [25, 35], "designations": ["US"] },
"scanCadence": "hourly",
"active": true,
"logoUrl": null,
"logoSt13": null
}

watchType is immutable; the field is rejected by the schema. Recreate the watch if you need a different type.

sources is server-managed and cannot be set via PATCH (same rationale as on POST).

attributes is replaced wholesale, not deep-merged — fetch, edit, send.

Example request

Terminal window
curl -sS -X PATCH https://api.trademarksentinel.app/api/v1/watches/ckxyz0000000abcdef \
-H "Authorization: Bearer ts_REPLACE_ME" \
-H "Content-Type: application/json" \
-d '{ "scanCadence": "hourly" }'

Example response — 200 OK

{
"data": {
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US", "FR"] },
"sources": ["global_trademark_database"],
"scanCadence": "hourly",
"active": true,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-30T08:14:01.000Z"
}
}

Status codes

CodeWhen
200Success.
400Malformed JSON.
401Missing or invalid API key.
403Tier below Team, or scope watches:write missing.
404The watch does not exist or is not visible to the calling key.
422Validation failed (immutable field touched, scanCadence faster than tier minimum, …).
429Rate limit exceeded.

DELETE /api/v1/watches/{id}

Soft-delete: sets active: false. The watch row and its alert history remain queryable; future scan runs are not scheduled.

To restore, PATCH with { "active": true }.

Required scope: watches:write.

Example request

Terminal window
curl -sS -X DELETE https://api.trademarksentinel.app/api/v1/watches/ckxyz0000000abcdef \
-H "Authorization: Bearer ts_REPLACE_ME"

Example response — 200 OK

{
"data": {
"id": "ckxyz0000000abcdef",
"watchType": "trademark",
"name": "Acme",
"attributes": { "niceClasses": [25], "designations": ["US", "FR"] },
"sources": ["global_trademark_database"],
"scanCadence": "daily",
"active": false,
"logoUrl": null,
"logoSt13": null,
"createdAt": "2026-04-30T08:14:01.000Z"
}
}

Status codes

CodeWhen
200Soft-delete succeeded (or watch was already inactive — idempotent).
401Missing or invalid API key.
403Tier below Team, or scope watches:write missing.
404The watch does not exist or is not visible to the calling key.
429Rate limit exceeded.