API & MCP.
Manage products, certifications, public pages, step templates and rental dossiers from your own code or an AI agent. Use the REST API, or connect any MCP client.
- REST API
- https://app.watch-tower.dev/api/v1
- MCP server
- https://app.watch-tower.dev/mcp
- Authentication
- Authorization: Bearer wt_live_…
Quickstart
1. Create a token
In the dashboard, go to Tokens de la API → Nuevo token → Generar token (managers only). Copy the secret; it’s shown once.
export WATCHTOWER_TOKEN=wt_live_…
2. Connect
claude mcp add --transport http watchtower https://app.watch-tower.dev/mcp \
--header "Authorization: Bearer $WATCHTOWER_TOKEN"
codex mcp add watchtower --url https://app.watch-tower.dev/mcp \
--bearer-token-env-var WATCHTOWER_TOKEN
Codex reads WATCHTOWER_TOKEN from the environment each time it starts.
{
"mcpServers": {
"watchtower": {
"type": "http",
"url": "https://app.watch-tower.dev/mcp",
"headers": { "Authorization": "Bearer ${WATCHTOWER_TOKEN}" }
}
}
}
Any MCP client that takes an HTTP URL and headers uses the same URL and Authorization header.
curl -s "https://app.watch-tower.dev/api/v1/products?per_page=20" \
-H "Authorization: Bearer $WATCHTOWER_TOKEN"
3. Try it
Ask your agent:
List my products whose last certification was denied.
Or call the API directly:
curl -s "https://app.watch-tower.dev/api/v1/products?last_certification_status=denied" \
-H "Authorization: Bearer $WATCHTOWER_TOKEN"
Authentication
Send your token in the Authorization: Bearer wt_live_… header. The same token works for the API and the MCP server.
Tokens are created and revoked only in the dashboard, by a manager. Revoking takes effect immediately, including any download links the token requested.
Requests, pagination, rate limits and errors
Every route sits under https://app.watch-tower.dev/api/v1. Requests and responses are JSON; dates are ISO 8601 in UTC, with microseconds.
Paginate lists with ?page=1&per_page=50 (50 by default, 100 at most). Every list returns:
{
"data": [ … ],
"page": 1, "per_page": 50,
"next_page": 2, // null on the last page
"total": 137, // every row, across all pages
"total_pages": 3 // at least 1
}
The full contract, with every schema and error code, is in /api/v1/openapi.yaml.
Rate limits
120 requests per minute per token, shared between the API and MCP. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Over the limit: 429 with Retry-After in seconds.
Errors
Errors have one shape, with the HTTP status:
{ "error": { "code": "not_found", "message": "…" } }
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_json | The body isn’t valid JSON. |
| 401 | unauthorized | Missing, unknown or revoked token. |
| 403 | forbidden | The operation needs a manager’s token. |
| 404 | not_found | The id doesn’t exist or isn’t yours. |
| 409 | not_publishable, external_ref_taken… | The request conflicts with the current state. |
| 422 | invalid_parameter… | A parameter is missing or invalid. |
| 429 | rate_limited… | Over the rate limit, or over the quota of emails to the buyer or to a dossier’s parties. |
Operations
Each row is an API route and an MCP tool with the same name. Routes are relative to /api/v1.
| Operation and tool | Route | What it does |
|---|---|---|
| list_products | GET/products | Lists products, newest first. Filter by external_ref or by the status of their latest certification (last_certification_status). Search with q: reference prefix or #id. |
| create_product | POST/products | Creates a product. Idempotent on external_ref. |
| get_product | GET/products/{id} | Returns a product with its latest certification. |
| update_product | PATCH/products/{id} | Changes or clears its external_ref. Changes its kind of object (challenge_medium: a phone, guided through 3 of its zones, or any other object) or what it is (asset_kind: a flat or anything else) while it has no certifications; the pair must be one your sector allows. |
| add_product_identifier | POST/products/{id}/identifiers | Adds an identifier: a flat’s address, its cadastral reference or a code of yours. |
| replace_product_identifier | POST/products/{id}/identifiers/{identifier_id}/replace | Corrects an identifier, keeping the old one in its history. |
| retire_product_identifier | POST/products/{id}/identifiers/{identifier_id}/retire | Retires an identifier that was wrong or no longer identifies it. |
| get_product_history | GET/products/{id}/history | Returns its identity history and its certifications, oldest first. |
| Operation and tool | Route | What it does |
|---|---|---|
| request_certification | POST/products/{product_id}/certifications | Requests a certification, recorded by the seller or by the buyer. For the buyer, Watchtower emails them, and they have record_days (1 to 365, 7 by default) to record it. Accepts a step template (step_template_id) or the steps of an earlier certification (steps_from_certification_id), and a dossier with its phase (dossier_id, phase). |
| list_certifications | GET/certifications | Lists certifications. Filter by status and product_id, order with sort. Search with q: WT- code or buyer email prefix. Returns the count for each status. |
| get_certification | GET/certifications/{id} | Returns its status, takes, /v/ page and what is public. |
| retry_certification | POST/certifications/{id}/retry | Retries a denied certification: opens the next attempt, up to 3 per chain. |
| resend_buyer_notice | POST/certifications/{id}/buyer_notice | Emails the buyer again, at the same address. |
| set_buyer_email | PUT/certifications/{id}/buyer | Moves the certification to another buyer email. |
| Operation and tool | Route | What it does |
|---|---|---|
| get_publication | GET/certifications/{certification_id}/publication | Returns what is public and the frame candidates. |
| set_publication | PUT/certifications/{certification_id}/publication | Sets the whole public set: video, candidate_ids, frame_ids. |
| get_candidate_image | GET/frame_candidates/{token} | Returns the JPEG of a frame candidate. |
| set_public_mode | PUT/certifications/{id}/public_view | Turns the page’s public mode on or off. |
| Operation and tool | Route | What it does |
|---|---|---|
| download_video | GET/certifications/{id}/video | The original video. |
| download_manifest | GET/certifications/{id}/manifest | Its C2PA manifest. |
| download_frame | GET/certifications/{id}/frames/{frame_id} | A sealed frame: a JPEG with its C2PA manifest embedded. |
| list_downloads | GET/certifications/{id}/downloads | Lists who downloaded the video, the manifest or a frame, through any channel. |
| Operation and tool | Route | What it does |
|---|---|---|
| list_step_templates | GET/step_templates | Lists your templates by name, with their steps. Search with q: part of the name. |
| create_step_template | POST/step_templates | Creates a template: a unique name and 1 to 40 steps, or a copy of a vertical’s ready-made template (from_vertical). |
| get_step_template | GET/step_templates/{id} | Returns a template with its steps in order. |
| update_step_template | PATCH/step_templates/{id} | Renames it, or replaces its steps, their rules, its capture profile or its vertical. Certifications already requested keep their copy. |
| delete_step_template | DELETE/step_templates/{id} | Archives it. Certifications already requested with it keep their steps. |
| list_verticals | GET/verticals | Lists the verticals of your sector, each with its ready-made template (today, vivienda: a rented flat, move-in and move-out). |
| Operation and tool | Route | What it does |
|---|---|---|
| list_dossiers | GET/dossiers | Lists your dossiers (EX-…), newest first. Filter by product_id and status. |
| create_dossier | POST/dossiers | Opens a dossier over a product, one open per product. response_days: 1 to 60, 10 by default. |
| get_dossier | GET/dossiers/{id} | Returns a dossier with its parties. |
| update_dossier | PATCH/dossiers/{id} | Changes an open dossier’s external_ref or response_days. |
| cancel_dossier | POST/dossiers/{id}/cancel | Cancels an open dossier. |
| add_dossier_party | POST/dossiers/{id}/parties | Names a party by email (inquilino, propietario or observador); Watchtower emails them. |
| revoke_dossier_party | DELETE/dossiers/{id}/parties/{party_id} | Revokes a party. |
| resend_party_notice | POST/dossiers/{id}/parties/{party_id}/notice | Emails a party of an open dossier again. |
| Operation and tool | Route | What it does |
|---|---|---|
| get_account | GET/account | Returns the company with its sector and the options it allows, the token’s user, the token itself (without the secret) and the team. |
| update_account_field_settings | PATCH/account/field_settings | Chooses how takes are uploaded: over Wi-Fi only (wifi_only, the default) or also over mobile data (cellular_allowed). Manager’s token only. |
| list_api_tokens | GET/api_tokens | Lists the company’s tokens, without their secret. Manager’s token only. |
Not available via API/MCP: recording (app only); tokens, sign-in, the team and its invitations, creating a company and the account page (dashboard only).
For MCP client authors
The server at /mcp uses Streamable HTTP, stateless: each call is a JSON-RPC POST answered with application/json. Business errors return a tool result with isError: true. download_video, download_manifest and download_frame return a signed link; fetch it with a GET, without an Authorization header.
Call a tool by hand
curl -s https://app.watch-tower.dev/mcp \
-H "Authorization: Bearer $WATCHTOWER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_products","arguments":{}}}'