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"}| Field | Type | Notes |
|---|---|---|
id | string | Server-assigned cuid(). Opaque. |
watchType | "trademark" | MVP only ships trademark. v2 adds domain, business_name, social_handle, app_listing. |
name | string | The brand / domain / handle being tracked. |
attributes | object | Type-specific shape, owned by the source adapter. For trademark: { niceClasses: number[], designations: string[] } (ISO 3166 alpha-2). |
sources | string[] | 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). |
active | boolean | false after a DELETE (soft-delete). |
logoUrl, logoSt13 | string | null | Trademark-only convenience fields. Always null for non-trademark watches. |
createdAt | string | ISO-8601. |
GET /api/v1/watches
List watches owned by the calling user (or the user’s organization, when applicable).
Query parameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit | integer | 50 | Max 200. See pagination. |
cursor | string | — | Opaque cursor from a previous response. |
watchType | string | — | Filter to one watch type. |
active | boolean | — | Filter by active flag. Pass false to see soft-deleted watches. |
Required scope: watches:read.
Example request
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
| Code | When |
|---|---|
200 | Success. |
401 | Missing or invalid API key. |
403 | Tier below Team, or scope watches:read missing. |
429 | Rate 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"}| Field | Required | Notes |
|---|---|---|
watchType | yes | MVP: "trademark". |
name | yes | 1–200 chars. |
attributes | yes | Shape depends on watchType. For trademark: niceClasses (int array, 1–45, 1–45 entries) and designations (ISO 3166 alpha-2 array, ≥ 1 entry). |
sources | n/a | Server-assigned; the canonical sources for watchType are used (["global_trademark_database"] for trademark in MVP). Client-supplied values are ignored. |
scanCadence | no | Defaults 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
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
| Code | When |
|---|---|
201 | Watch created. |
400 | Malformed JSON. |
401 | Missing or invalid API key. |
402 | Quota 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. |
403 | Tier below Team, or scope watches:write missing. |
422 | Validation failed (unknown watchType, missing attributes.niceClasses, …). The error.details carries fieldErrors from the server-side schema. |
429 | Rate limit exceeded. |
GET /api/v1/watches/{id}
Read a single watch.
Required scope: watches:read.
Example request
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
| Code | When |
|---|---|
200 | Success. |
401 | Missing or invalid API key. |
403 | Tier below Team, or scope watches:read missing. |
404 | The watch does not exist or is not visible to the calling key. |
429 | Rate 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
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
| Code | When |
|---|---|
200 | Success. |
400 | Malformed JSON. |
401 | Missing or invalid API key. |
403 | Tier below Team, or scope watches:write missing. |
404 | The watch does not exist or is not visible to the calling key. |
422 | Validation failed (immutable field touched, scanCadence faster than tier minimum, …). |
429 | Rate 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
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
| Code | When |
|---|---|
200 | Soft-delete succeeded (or watch was already inactive — idempotent). |
401 | Missing or invalid API key. |
403 | Tier below Team, or scope watches:write missing. |
404 | The watch does not exist or is not visible to the calling key. |
429 | Rate limit exceeded. |