# AyeWatch API, Complete Documentation

Every page of the AyeWatch API reference, concatenated.

Source: https://ayewatch.ai/documentation
Generated: 2026-10-07T12:09:19.739Z

## Contents

- [AyeWatch API](https://ayewatch.ai/documentation), Monitor Internet Information and receive real-time webhook events via the AyeWatch REST API.
- [Authentication](https://ayewatch.ai/documentation/authentication), All API requests must be authenticated with a Bearer API key.
- [Topics API](https://ayewatch.ai/documentation/topics), Create and manage monitoring topics via REST. All endpoints require a Bearer API key.
- [MCP server](https://ayewatch.ai/documentation/mcp), Connect an AI assistant to your AyeWatch monitors using your existing API key.
- [Webhooks](https://ayewatch.ai/documentation/webhooks), Receive real-time events when AyeWatch detects changes in your monitored topics.
- [Errors](https://ayewatch.ai/documentation/errors), Standard error format and HTTP status codes returned by the AyeWatch API.

---

# AyeWatch API

Monitor Internet Information and receive real-time webhook events via the AyeWatch REST API.

Source: https://ayewatch.ai/documentation

Base URL: `https://ayewatch.ai/api/v1`

## What is the AyeWatch API?

The AyeWatch API lets you programmatically manage monitoring internet information topics and
receive real-time notifications when content changes are detected. Instead of manually checking
for updates, you define topics to monitor and AyeWatch delivers structured webhook events to your
application whenever something new happens.

- Create, list, update, and delete monitoring topics via REST
- Set your webhook endpoint and rotate its signing secret via REST
- Receive `topic.update` webhook events with AI-generated summaries
- Verify webhook signatures with HMAC-SHA256
- Authenticate all requests with a Bearer API key

## Quick Start

Get started in two steps: create a topic to monitor, then set up a webhook to receive updates.

### Step 1, Create a topic (monitors a URL)

Request:

```bash
curl -X POST https://ayewatch.ai/api/v1/topics \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "https://openai.com/news",
    "interval": "1_hour"
  }'
```

Response, 201 Created:

```json
{
  "data": {
    "id": 1042,
    "name": "https://openai.com/news",
    "description": null,
    "is_active": true,
    "interval": "1_hour",
    "created_at": "2026-08-03T12:00:00Z"
  }
}
```

### Step 2, Configure your webhook endpoint

Set your webhook URL with the API, or in the [Notification settings](https://ayewatch.ai/settings/webhooks).
AyeWatch will POST an event to your endpoint whenever a monitor finds something new.

```bash
curl -X PUT https://ayewatch.ai/api/v1/webhook \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://example.com/hooks/ayewatch"}'
```

The response includes the `secret` you use to verify each delivery's signature.

Example webhook payload:

```json
{
  "topic_id": 1042,
  "topic_name": "https://openai.com/news",
  "headline": "GPT-5 announced",
  "content": "OpenAI today announced GPT-5...",
  "created_at": "2026-03-08T13:00:00Z"
}
```

## Explore the Docs

- [Authentication](https://ayewatch.ai/documentation/authentication), API key format, Bearer headers, and security tips.
- [Topics API](https://ayewatch.ai/documentation/topics), Create, list, update, and delete monitoring topics.
- [Webhooks](https://ayewatch.ai/documentation/webhooks), Webhook API, event format, HMAC signature verification, and retry behavior.
- [Errors](https://ayewatch.ai/documentation/errors), HTTP status codes and standard error response shape.

## Markdown for LLMs and Agents

Every page in these docs is also served as plain markdown. Append `.md` to any documentation URL
(for example `https://ayewatch.ai/documentation/topics.md`), or request the HTML URL with an
`Accept: text/markdown` header. The complete reference in one file is at
`https://ayewatch.ai/documentation/all.md`, and site-wide summaries live at `https://ayewatch.ai/llms.txt`
and `https://ayewatch.ai/llms-full.txt`.

---

# Authentication

All API requests must be authenticated with a Bearer API key.

Source: https://ayewatch.ai/documentation/authentication

## Generating API Keys

API keys are available on paid plans. Generate and manage your keys in the [API access settings](https://ayewatch.ai/settings/api).
You can create up to the limit for your plan and revoke them at any time.

> **Plan requirement:** API keys are only available on paid plans. Upgrade your plan to enable API
> access.

## Rate Limits

Each API key is limited to 120 requests per minute by default. Every authenticated response includes
`RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, and `X-Request-Id` headers. A 429
response also includes `Retry-After`.

## Key Format

All AyeWatch API keys follow this format:

```text
aw_live_<96 hex characters>
```

Example: `aw_live_a1b2c3d4e5f6...` (total length: ~104 characters)

## Making Authenticated Requests

Pass your API key in the `Authorization` header as a Bearer token:

```bash
curl https://ayewatch.ai/api/v1/topics \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY"
```

## Security Tips

- **Never expose keys client-side.** API keys grant full access to your topics and webhook settings,
  including the webhook signing secret. Keep them in environment variables or secrets managers on
  your server.
- **Rotate keys regularly.** Revoke and regenerate keys periodically, or immediately if you suspect
  a leak.
- **Use one key per integration.** Create separate keys for different services so you can revoke
  individual ones without affecting others.
- **Never commit keys to source control.** Use `.env` files and add them to `.gitignore`.

## Error Responses

Missing or invalid API keys return a `401 Unauthorized` response:

```json
{
  "error": {
    "message": "Unauthorized",
    "status": 401
  }
}
```

See the [Errors reference](https://ayewatch.ai/documentation/errors) for all status codes.

---

# Topics API

Create and manage monitoring topics via REST. All endpoints require a Bearer API key.

Source: https://ayewatch.ai/documentation/topics

## Scope

These endpoints manage every monitor in your account, including monitors created on the website or mobile app.
API-created monitors appear in the same monitor list and follow your account's notification preferences.
The same plan limits and monitoring credits apply across all platforms. API calls do not have a separate bill.
Configure a [webhook](https://ayewatch.ai/documentation/webhooks) in settings or with `PUT /api/v1/webhook` to receive update notifications in your application.

## Topic types (on creation, detected from `name`)

- `web_page`, if `name` is a valid HTTP or HTTPS URL, AyeWatch monitors that specific page.
- `subject`, if `name` is plain text, AyeWatch monitors that keyword/subject across the internet.

## Endpoints

| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/api/v1/topics` | Create a topic |
| `GET` | `/api/v1/topics` | List topics (paginated) |
| `GET` | `/api/v1/topics/:id` | Get a topic |
| `PUT` | `/api/v1/topics/:id` | Update a topic |
| `DELETE` | `/api/v1/topics/:id` | Delete a topic |

## POST /api/v1/topics

Create a new monitoring topic.

Active topics are limited by your plan's active-topic limit and allowed intervals, and turning a topic
on needs at least one monitoring credit. Accounts also have a default storage limit of 3,000 topics,
counting every topic you have, active or paused, created through the API or in the app.

### Request Body (JSON)

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | Required unless template or web_page_urls is supplied. A valid URL (e.g. "https://openai.com/news") to monitor a page, or plain text (e.g. "OpenAI announcements") to monitor a subject. Topic type is auto-detected. |
| `interval` | string | No | Required for custom monitoring unless template supplies it. Check interval. One of: "0_5_hours", "1_hour", "6_hours", "12_hours", "1_day", "2_days", "3_5_days", "7_days", "14_days", "30_days", "182_days", "365_days". Your plan must unlock the interval you choose. |
| `description` | string | No | What should alert you. Describe the change you care about, e.g. "Tell me when a new AI model drops." |
| `is_active` | boolean | No | Whether to start monitoring immediately. Default: true. |
| `verification_level` | string | No | How much corroboration is required before you're alerted: "instant", "balanced", or "verified". Defaults to "instant" for pages and "balanced" for subjects. |
| `type` | string | No | "subject" or "web_page". Detected from name on creation if omitted; preserved on edits. |
| `web_page_urls` | string[] | No | Pages to monitor together. Replaces the source list. Your plan's page limit applies. |
| `monitoring_mode` | string | No | "auto" or "custom". Defaults to custom. |
| `auto_min_interval`, `auto_max_interval` | string or null | No | Interval bounds for automatic monitoring. |
| `timezone` | string | No | IANA timezone, for example Asia/Kolkata. |
| `template` | object | No | Template selection: id, version, inputs, interval. Keep the template id on edits; do not mix with direct configuration overrides. |
| `triggers` | array | No | Precise conditions to alert on, instead of relying on the description alone. See Triggers below. Max 4. |

### Triggers (optional)

A trigger states a condition in structured form. When you supply them, AyeWatch still falls back to
interpreting your `description` for anything the triggers don't cover.

- `entity`, what is being watched, e.g. "GPT-5".
- `attribute`, the property of it, e.g. "price".
- `op`, one of `<`, `<=`, `>`, `>=`, `=`, `changed`, `crossed_above`, `crossed_below`.
- `value`, must be numeric for the comparison operators (everything except `changed`).
- `source_url`, optional, restricts the trigger to one source.

Example, alert on a precise condition:

```bash
curl -X POST https://ayewatch.ai/api/v1/topics \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "NVIDIA H100 pricing",
    "interval": "6_hours",
    "verification_level": "verified",
    "triggers": [
      {
        "entity": "H100 80GB",
        "attribute": "price",
        "op": "<",
        "value": 25000,
        "value_type": "number"
      }
    ]
  }'
```

Example, monitor a URL:

```bash
curl -X POST https://ayewatch.ai/api/v1/topics \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "https://openai.com/news",
    "interval": "1_hour"
  }'
```

Example, monitor a subject / keyword:

```bash
curl -X POST https://ayewatch.ai/api/v1/topics \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "OpenAI product announcements",
    "interval": "1_day"
  }'
```

Response, 201 Created:

```json
{
  "data": {
    "id": 1042,
    "name": "https://openai.com/news",
    "description": null,
    "is_active": true,
    "interval": "1_hour",
    "created_at": "2026-08-03T12:00:00Z"
  }
}
```

Create, read, list, and update responses contain only id, name, description, interval, is_active, and created_at.
Use id to fetch, edit, or delete the monitor. is_active is true for an active monitor and false for a paused monitor.
Successful changes are saved immediately; monitoring may take a few minutes to start.

## GET /api/v1/topics

List all monitors for the authenticated account, paginated.

### Query Parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `page` | number | No | Page number. Default: 1. |
| `page_size` | number | No | Results per page. Max: 100. Default: 20. |
| `is_active` | boolean | No | Filter by active status. Pass "true" or "false". |
| `interval` | string | No | Filter by interval. One of: "1_hour", "6_hours", etc. Legacy values on older topics are also accepted as a filter. |
| `created_after` | string (ISO 8601) | No | Only topics created after this datetime. |
| `created_before` | string (ISO 8601) | No | Only topics created before this datetime. |

Request:

```bash
curl "https://ayewatch.ai/api/v1/topics?page=1&page_size=10&is_active=true" \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY"
```

Response, 200 OK:

```json
{
  "data": [
    {
      "id": 1042,
      "name": "https://openai.com/news",
      "description": null,
      "is_active": true,
      "interval": "1_hour",
      "created_at": "2026-03-08T12:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 10,
    "has_more": false
  }
}
```

## GET /api/v1/topics/:id

Retrieve a single topic by ID.

Request:

```bash
curl https://ayewatch.ai/api/v1/topics/1042 \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY"
```

Response, 200 OK:

```json
{
  "data": {
    "id": 1042,
    "name": "https://openai.com/news",
    "description": null,
    "is_active": true,
    "interval": "1_hour",
    "created_at": "2026-03-08T12:00:00Z"
  }
}
```

Responses contain the monitor's basic details.
Keep any template selection and inputs in your integration if you need to edit them later.

## PUT /api/v1/topics/:id

Update a topic. Send only the fields you want to change.
Omitted settings are preserved. Name, description, or source changes clear generated alert rules unless replacement triggers are supplied.
Use template inputs to edit template monitors. is_active and timezone can be updated independently.
List account monitors and use a returned ID to edit a monitor created on the website or mobile app.

### Request Body (JSON), all fields optional

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | No | New subject name or first page URL. Other page URLs are preserved. Use type to change the monitor type. |
| `description` | string | No | New description. |
| `interval` | string | No | New check interval. One of: "1_hour", "6_hours", etc. Your plan must unlock the interval you choose. |
| `is_active` | boolean | No | Enable or disable the topic. |
| `verification_level` | string | No | New verification level: "instant", "balanced", or "verified". |
| `type` | string | No | "subject" or "web_page". Detected from name on creation if omitted; preserved on edits. |
| `web_page_urls` | string[] | No | Pages to monitor together. Replaces the source list. Your plan's page limit applies. |
| `monitoring_mode` | string | No | "auto" or "custom". Defaults to custom. |
| `auto_min_interval`, `auto_max_interval` | string or null | No | Interval bounds for automatic monitoring. |
| `timezone` | string | No | IANA timezone, for example Asia/Kolkata. |
| `template` | object | No | Template selection: id, version, inputs, interval. Keep the template id on edits; do not mix with direct configuration overrides. |
| `triggers` | array | No | Replaces the existing triggers. Send an empty array to clear them; omit the field to leave them untouched. |

The request must contain at least one of these fields. Unknown fields are rejected with a `400`
rather than silently ignored.

Request:

```bash
curl -X PUT https://ayewatch.ai/api/v1/topics/1042 \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"interval": "6_hours", "is_active": false}'
```

Response, 200 OK:

```json
{
  "data": {
    "id": 1042,
    "name": "https://openai.com/news",
    "description": null,
    "is_active": false,
    "interval": "6_hours",
    "created_at": "2026-03-08T12:00:00Z"
  }
}
```

## DELETE /api/v1/topics/:id

Permanently delete a topic and its schedule. This action cannot be undone.

Request:

```bash
curl -X DELETE https://ayewatch.ai/api/v1/topics/1042 \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY"
```

Response, 200 OK:

```json
{
  "data": {
    "deleted": true
  }
}
```

---

# MCP server

Connect an AI assistant to your AyeWatch monitors using your existing API key.

Source: https://ayewatch.ai/documentation/mcp

## Connect your assistant

The MCP endpoint is `https://ayewatch.ai/api/mcp`. It uses Streamable HTTP and accepts your existing AyeWatch API key in the `Authorization: Bearer aw_live_...` header on every request.

Generate a key in [API access settings](https://ayewatch.ai/settings/api). API and MCP access are available on every paid plan.

For [Cursor](https://cursor.com/docs/context/mcp), add this entry to your MCP configuration. Other clients need the same URL, Streamable HTTP transport, and Authorization header. Replace the placeholder with your key and keep the configuration private.

```json
{
  "mcpServers": {
    "ayewatch": {
      "url": "https://ayewatch.ai/api/mcp",
      "headers": {
        "Authorization": "Bearer aw_live_YOUR_API_KEY"
      }
    }
  }
}
```

## Connect with OAuth

Supported registered apps can offer an AyeWatch sign-in flow when OAuth connections are enabled. Use the MCP endpoint above, sign in to AyeWatch, and review the requested access before selecting Allow access. You do not need to share an API key with the app.

Review the permissions shown before connecting: read access lets the app view your monitors; write access lets it create, change, pause, and delete them. Active monitors use your plan's monitoring credits. A paid plan is required. Access expires after 30 days, or sooner if you disconnect. OAuth connections are limited to registered integrations; this does not mean AyeWatch is already listed in an app directory.

To stop an app's access, open [Connected apps](https://ayewatch.ai/settings/connections) and select Disconnect. Future MCP requests are rejected after disconnection. Existing monitors keep their current schedules, and copies already saved by the app are not deleted.

## Available tools

- `list_topics`: list monitors with optional `page`, `page_size`, `is_active`, `interval`, `created_after`, and `created_before` filters. Page numbers and sizes are integers. The default page size is 20, with a maximum of 100. Responses include `pagination.has_more`.
- `get_topic`: get a monitor by numeric `topic_id`.
- `create_topic`: create a monitor using the same fields as the REST API. Supply a name, web-page URLs, or a template, plus an interval for custom monitoring or `monitoring_mode: "auto"`.
- `update_topic`: supply `topic_id` and a `changes` object with only the fields you want to update. Set `changes.is_active` to `false` to pause or `true` to resume.
- `delete_topic`: permanently delete a monitor and its schedule by `topic_id`.
- `get_webhook`: get the account's webhook URL and whether delivery is on. `data` is `null` when none is configured.
- `set_webhook`: create the webhook with `webhook_url`, or change only the fields you supply. Set `is_active` to `false` to pause delivery or `true` to resume.
- `rotate_webhook_secret`: replace the signing secret. The old secret stops working immediately.

The webhook tools are available only when you connect with an API key. OAuth connections cannot read or change your webhook, because it receives every alert and its secret signs them.

The webhook tools never return the signing secret, so it stays out of AI conversations. Read it in [Notification settings](https://ayewatch.ai/settings/webhooks) or with `GET /api/v1/webhook`. AyeWatch emails the account owner whenever the webhook URL is set or changed.

See [Topics API](https://ayewatch.ai/documentation/topics) for monitor fields and supported intervals.

Example `create_topic` arguments:

```json
{
  "name": "https://example.com/releases",
  "description": "Alert me when a new version is released.",
  "monitoring_mode": "auto",
  "is_active": false
}
```

This example creates a paused monitor. Review it, then call `update_topic` with its returned ID and `changes: {"is_active": true}` to start monitoring.

## Access and limits

MCP manages the same monitors as the website, mobile app, and REST API. Existing plan limits, monitoring credits, ownership checks, and API-key revocation apply. Active monitors consume credits as checks run.

MCP and REST share the API key's rate limit, 120 requests per minute by default. Each MCP POST, including initialization and tool discovery, counts once. Send one MCP message per POST; batches are rejected. Responses include `X-Request-Id` and `RateLimit-*` headers. A 429 response includes `Retry-After`.
OAuth connections have the same default limit, counted per AyeWatch account and connected app. OAuth tokens work only on the MCP endpoint; the REST API continues to require an API key.

Authentication failures return HTTP 401 or 403. Tool failures return an MCP result with `isError: true`; monitor-service failures include an error message and HTTP-equivalent status. Tool results include both text and structured JSON.

The endpoint is stateless. It returns JSON over Streamable HTTP and requires no session ID. GET and DELETE return 405 because it does not offer a persistent event stream or sessions to delete. Browser requests must use the same origin as the endpoint.

Updates continue to arrive through your configured notifications and [webhooks](https://ayewatch.ai/documentation/webhooks). These tools manage monitors and the webhook; they do not provide an alert-history feed.

---

# Webhooks

Receive real-time events when AyeWatch detects changes in your monitored topics.

Source: https://ayewatch.ai/documentation/webhooks

## Setup

Configure your webhook endpoint URL in the [Notification settings → Webhook](https://ayewatch.ai/settings/webhooks),
or with the Webhook API below. Your endpoint must:

- Be publicly accessible over **HTTPS**
- Respond with a `2xx` status within **10 seconds**
- Accept `POST` requests with a JSON body

> **Note:** Failed deliveries are retried with exponential backoff for up to 10 attempts. Make
> handlers idempotent because a delivery may be attempted more than once.

## Webhook API

Manage the endpoint with your API key. Each account has one webhook, and it receives updates from
every monitor in the account.

| Method | Path | Description |
| --- | --- | --- |
| `GET` | `/api/v1/webhook` | Get the webhook. `data` is `null` when none is configured. |
| `PUT` | `/api/v1/webhook` | Create the webhook, or update the fields you send |
| `POST` | `/api/v1/webhook/rotate-secret` | Replace the signing secret |

### PUT /api/v1/webhook

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `webhook_url` | string | To create | Public HTTPS URL. Private, loopback, and internal addresses are rejected. |
| `is_active` | boolean | No | `false` pauses delivery and `true` resumes it. New webhooks start active. |

Send at least one field. Omitted fields keep their current values, and changing the URL keeps the
signing secret. Sending only `is_active` before a webhook exists returns `404`.

Request:

```bash
curl -X PUT https://ayewatch.ai/api/v1/webhook \
  -H "Authorization: Bearer aw_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://example.com/hooks/ayewatch"}'
```

Response, 200 OK:

```json
{
  "data": {
    "id": "4f6c2a1e-8b0d-4c7a-9e35-1d2f6b8a7c90",
    "webhook_url": "https://example.com/hooks/ayewatch",
    "is_active": true,
    "secret": "<64 hex characters>",
    "created_at": "2026-10-06T12:00:00Z",
    "updated_at": "2026-10-06T12:00:00Z"
  }
}
```

`GET /api/v1/webhook` and `POST /api/v1/webhook/rotate-secret` return the same object. Rotating
before a webhook exists returns `404`.

> **Note:** Responses include `secret`, which signs every delivery. Anyone holding one of your API
> keys can read it, so if a key leaks, revoke the key and rotate the secret.

AyeWatch emails the account owner whenever the webhook URL is set or changed, whether from
settings, the API, or MCP. Pausing, resuming, and rotating the secret do not send an email.

## When Webhooks Fire

AyeWatch sends a webhook only when new content is detected for a monitored topic. No request is sent
when a topic is checked but nothing has changed.

## Webhook Payload

AyeWatch sends a `POST` request with a JSON body to your endpoint:

```json
{
  "topic_id": 1042,
  "topic_name": "https://openai.com/news",
  "update_id": 7891,
  "headline": "GPT-5 announced",
  "content": "OpenAI today announced the release of GPT-5...",
  "content_detail": "...",
  "created_at": "2026-03-08T12:00:00Z"
}
```

| Field | Type | Description |
| --- | --- | --- |
| `topic_id` | number | Integer ID of the monitored topic (matches the id from the Topics API). |
| `topic_name` | string | Name or URL of the topic as set on creation. |
| `update_id` | number | Integer ID of this specific update. Use it to deduplicate retried deliveries. |
| `headline` | string \| undefined | Short AI-generated summary of the detected change. |
| `content` | string \| undefined | Full content of the update. |
| `content_detail` | string \| undefined | Additional detail about the update. |
| `created_at` | string (ISO 8601) | Timestamp when the update was created. |
| `novelty_class` | string \| undefined | How new this is relative to what was already reported on the topic. |
| `score` | number \| undefined | Internal confidence in the update's significance. |
| `evidence` | array \| undefined | Supporting quotes with their source URLs: [{ quote, source_url }]. |
| `trigger` | string \| undefined | Which of the topic's triggers fired, when the update came from one. |

## Signature Verification

Every webhook request includes an `X-AyeWatch-Signature` header containing an HMAC-SHA256 signature
signed with your webhook secret. Verify against the raw request bytes, not a re-serialized version
of the parsed payload.

Header format: `X-AyeWatch-Signature: sha256=<hex_digest>`

Always verify this signature before processing webhook events. Reject events that fail verification.

Node.js / Express:

```js
const crypto = require("crypto");

function verifyWebhookSignature(rawBody, signature, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  // Use timingSafeEqual to prevent timing attacks
  const expectedBuffer = Buffer.from(expected, "utf8");
  const signatureBuffer = Buffer.from(signature, "utf8");

  if (expectedBuffer.length !== signatureBuffer.length) return false;
  return crypto.timingSafeEqual(expectedBuffer, signatureBuffer);
}

// Express example
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
  const signature = req.headers["x-ayewatch-signature"];
  const isValid = verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const event = JSON.parse(req.body);
  console.log("Update received for topic:", event.topic_id, event.headline);
  res.status(200).json({ received: true });
});
```

Python / FastAPI:

```python
import hashlib
import hmac

def verify_webhook_signature(raw_body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        raw_body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

# FastAPI example
from fastapi import FastAPI, Request, HTTPException

app = FastAPI()

@app.post("/webhook")
async def webhook(request: Request):
    raw_body = await request.body()
    signature = request.headers.get("x-ayewatch-signature", "")

    if not verify_webhook_signature(raw_body, signature, WEBHOOK_SECRET):
        raise HTTPException(status_code=401, detail="Invalid signature")

    event = await request.json()
    print("Update received for topic:", event["topic_id"], event.get("headline"))
    return {"received": True}
```

## Delivery Behavior

- **Retries:** Non-2xx responses and timeouts are retried with exponential backoff, up to 10
  attempts.
- **10-second timeout:** Requests that don't complete within 10 seconds are abandoned.
- **HTTPS only:** Plain HTTP endpoints are not supported.
- **Order not guaranteed:** Events may arrive out of order under load.
- **Additive payloads:** New fields may be added over time. Ignore any you don't recognise rather
  than rejecting the request.

## Rotating Your Secret

You can rotate the signing secret from the [Notification settings → Webhook](https://ayewatch.ai/settings/webhooks)
or with `POST /api/v1/webhook/rotate-secret`.
Rotation takes effect immediately, there is no overlap window, so deliveries signed with the new
secret will fail verification until your receiver is updated. Deploy the new secret first, or rotate
during a window where a few retried deliveries are acceptable.

---

# Errors

Standard error format and HTTP status codes returned by the AyeWatch API.

Source: https://ayewatch.ai/documentation/errors

## Error Response Shape

All API errors return a consistent JSON structure:

```json
{
  "error": {
    "message": "Human-readable error description",
    "status": 400
  }
}
```

The `status` field mirrors the HTTP response status code. Always check the HTTP status first, then
read `error.message` for details.

## HTTP Status Codes

| Status | Name | When it occurs |
| --- | --- | --- |
| `400` | Bad Request | The request body or parameters are invalid or missing required fields. Also returned when the interval you asked for isn't unlocked by your plan. |
| `401` | Unauthorized | API key is missing, malformed, or does not exist. |
| `403` | Forbidden | The API key is valid, but the account is on Free Preview, or the request would exceed a plan limit such as active topics, or the account has no monitoring credits left. |
| `404` | Not Found | The requested resource (topic ID) does not exist or belongs to another user, or no webhook is configured yet. |
| `409` | Conflict | The request conflicts with your existing topics, either a topic with that name already exists, or another change to your topics was still being applied. The second case is safe to retry after a short pause. |
| `429` | Too Many Requests | Rate limit exceeded for this API key. Wait for the number of seconds given in the Retry-After header before retrying. |
| `500` | Internal Server Error | An unexpected error occurred on our side. Please retry or contact support. |

## Example Error Responses

401, Missing or invalid API key:

```json
{
  "error": {
    "message": "Unauthorized",
    "status": 401
  }
}
```

403, Upgrade required:

```json
{
  "error": {
    "message": "API access requires a paid plan",
    "status": 403
  }
}
```

403, Max API keys reached:

```json
{
  "error": {
    "message": "You have reached the maximum of 10 active API keys",
    "status": 403
  }
}
```

403, Plan limit reached:

```json
{
  "error": {
    "message": "You're out of monitoring credits. Turn this monitor on once your credits refill, or upgrade for more.",
    "status": 403
  }
}
```

404, Topic not found:

```json
{
  "error": {
    "message": "Topic not found",
    "status": 404
  }
}
```

409, Duplicate topic name:

```json
{
  "error": {
    "message": "A topic with this name already exists",
    "status": 409
  }
}
```

409, Another change was in progress (retry):

```json
{
  "error": {
    "message": "Timed out waiting for another monitor change to finish.",
    "status": 409
  }
}
```

## Need Help?

If you encounter a persistent 500 error or unexpected behavior, please [contact support](https://ayewatch.ai/contact)
with the request details and timestamps.

---
