# MCPNotify API & MCP server — LLM Integration Guide

You are an autonomous agent that has been given a **base URL** and an **API key** for the service documented below. This document is self-contained: it tells you everything needed to call the API. Follow it literally.

- **Base URL:** `https://mcpnotify.com`
- **API key:** provided to you separately; it starts with `sk_`.
- **All paths below are relative to the base URL** (e.g. `https://mcpnotify.com/api/v1/health`).

> Generated from the app's single source of truth. If an endpoint
> behaves differently from what you read here, trust the live response
> and report the mismatch — the docs are meant to be authoritative.

## Essentials

### Base URL

All endpoints live under `https://mcpnotify.com/api/v1`. `https://mcpnotify.com` is the origin you were given (scheme + host, e.g. `https://example.com`). Do not add a trailing slash.

### Authentication

Every endpoint requires an API key sent as a Bearer token. There are two kinds:
- **Project keys** `mcpn_…` — for sending: `/api/v1/messages`, `/api/v1/projects` and the MCP server `/mcp` (= `/api/v1/mcp`). `Authorization: Bearer mcpn_your_key_here`. Create them on **API keys** in the dashboard; a key covers one project, several, or all of its owner's projects, may expire, and can be revoked. An unknown, expired or revoked key returns `401`.
- **Account keys** `sk_…` — for the account endpoints (health, stats, users, files): `Authorization: Bearer sk_your_key_here`, created under Profile → API Keys. A missing or invalid key returns `401 { "error": "Invalid or missing API key" }`.

### Sending a notification (the 10-second version)

`POST https://mcpnotify.com/api/v1/messages` with `{"app":"<who is sending>","message":"<text>"}` and a `mcpn_` key. `app` and `message` are required; `title`, `url` and `project` are optional. Without `project` the message goes to every project the key covers. Every browser, Telegram chat and Slack channel subscribed to the project gets it (instantly, or bundled into a digest if that subscriber chose one).

### MCP

`https://mcpnotify.com/mcp` is a Model Context Protocol server (Streamable HTTP transport, stateless, JSON responses) with two tools: `send_notification` (`app`, `message` required; `title`, `url`, `project`, `body` and files optional — `attachments: [{filename, text | content_base64, content_type}]` up to 50 MB each, or `attachment_ids` from `POST /api/v1/attachments`) and `list_projects`. Authenticate with the same `Authorization: Bearer mcpn_…` header. Claude Code: `claude mcp add --transport http mcpnotify https://mcpnotify.com/mcp --header "Authorization: Bearer mcpn_…"`. Other clients: `{"type":"http","url":"https://mcpnotify.com/mcp","headers":{"Authorization":"Bearer mcpn_…"}}`.

### Content type

Responses are JSON unless noted (file download returns raw bytes). Request bodies are JSON (`Content-Type: application/json`) except file upload, which is `multipart/form-data`.

### Rate limiting

Requests are rate limited per API key. When you exceed a limit you get `429` (or `403` if the limit is configured to block) with an `error` message and, when applicable, a `Retry-After` header (seconds). Back off and retry.

### Errors

Errors are JSON with an `error` string and a matching HTTP status (`400` bad input, `401` unauthenticated, `403` forbidden, `404` not found, `413` payload too large, `429` rate limited, `500` server error).

## Quick start

```bash
# 1. Verify your key works
curl https://mcpnotify.com/api/v1/health -H "Authorization: Bearer sk_your_key_here"
# A 200 with {"status":"healthy",...} means you are authenticated.
```

## Endpoints

### POST /api/v1/messages

**Send a notification** — Sends one message to every subscriber of the target project(s): browser push, Telegram and Slack. Delivery happens in the background; the response tells you how many subscribers it reached (`recipients`) and how many of those hold it for a digest (`queuedForDigest`).

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** A project key (`mcpn_…`) that covers the target project. Rate limited per key (default 120/minute).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `app` | body | string (1–100) | yes | Who is sending: your app, script, agent or service. Shown as the notification heading. |
| `message` | body | string (1–4000) | yes | The notification text. |
| `title` | body | string (≤200) | no | Heading to use instead of `app`. |
| `url` | body | string (http/https) | no | Opened when the notification is clicked. Default: the subscriber's settings page. |
| `project` | body | string | string[] | no | Project slug or id (or a list). Omit to send to every project the key covers. Unknown → 404. |
| `body` | body | string | object | array (≤ 1 MB) | no | Long payload — text, Markdown, logs or JSON (objects are pretty-printed). Not in the notification itself: clicking the notification opens a message page that shows it in full. |
| `attachments` | body | string[] (≤ 20) | no | Attachment ids from `POST /api/v1/attachments` (unused, uploaded by the same account, within 24 h). The message page previews/streams images, video, audio, PDF and text, and offers a download. |

**Request**

```bash
curl -X POST https://mcpnotify.com/api/v1/messages \
  -H "Authorization: Bearer mcpn_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"app":"nightly-backup","message":"Backup finished in 4m12s","project":"ops"}'
```

**Response**

```
{
  "ok": true,
  "recipients": 3,
  "messages": [
    {
      "id": "k2j4h5g6f7d8s9",
      "project": { "id": "p1a2b3c4d5", "slug": "ops", "name": "Ops" },
      "recipients": 3,
      "queuedForDigest": 1
    }
  ]
}
```

> Status `201` on success. `400` for a missing/invalid field (the `error` names it), `401` for a bad key, `404` for an unknown `project`, `429` when rate limited.
>
> One message row is created per target project, so a key covering three projects without `project` produces three entries in `messages`.
>
> With `body` or `attachments`, each entry has a `viewUrl` (the message page; also what the notification opens — it links on to your `url`) and an `attachments` count. Notifications with files get a 📎 in front of the text.
>
> **Multipart:** instead of JSON you may send `multipart/form-data` with the same fields (`app`, `message`, `title`, `url`, `project`, `body`, `attachments`) plus file parts — every file part becomes an attachment (≤ 50 MB each; use `POST /api/v1/attachments` for bigger files). `curl -F app=ci -F message=done -F files=@report.pdf …`
>
> Attachments are kept 30 days, then deleted.

---

### POST /api/v1/attachments

**Upload a (large) attachment** — Uploads one file as the **raw request body** (streamed straight to storage — up to 2 GB) and returns an id. Pass the id in `attachments` of `POST /api/v1/messages` (or `attachment_ids` of the MCP tool) within 24 hours; unused uploads are deleted. Readers never see a storage credential: the message page mints a short-lived read link per file for preview, streaming and download.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** A project key (`mcpn_…`). Counts against the send rate limit.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `filename` | query | string | no | File name shown to readers (also accepted as `X-Filename` header). Characters outside `[A-Za-z0-9._-]` become `_`. |
| `Content-Type` | header | string | no | The file's MIME type (decides the preview: image/*, video/*, audio/*, application/pdf, text/*, application/json). Guessed from the extension when omitted or `application/octet-stream`. |

**Request**

```bash
curl -X POST "https://mcpnotify.com/api/v1/attachments?filename=screen-recording.mp4" \
  -H "Authorization: Bearer mcpn_your_key_here" \
  -H "Content-Type: video/mp4" \
  --data-binary @screen-recording.mp4
```

**Response**

```
{
  "id": "att_3Fq9xT2mWpL8vZk1aB0c",
  "filename": "screen-recording.mp4",
  "contentType": "video/mp4",
  "size": 48213377,
  "sha256": "9c1e…",
  "expiresAt": "2026-10-03T16:00:00.000Z"
}
```

> Then: `curl -X POST {BASE_URL}/api/v1/messages -H "Authorization: Bearer mcpn_…" -H "Content-Type: application/json" -d '{"app":"qa","message":"Repro video attached","attachments":["att_3Fq9…"]}'`
>
> `413` above the size limit, `501` when the server has no storage configured. Do not send multipart here — send the bytes as the body.

---

### GET /api/v1/projects

**List the projects a key can send to** — Returns the projects covered by the calling project key — use a `slug` as the `project` field when sending.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** A project key (`mcpn_…`).

**Request**

```bash
curl https://mcpnotify.com/api/v1/projects \
  -H "Authorization: Bearer mcpn_your_key_here"
```

**Response**

```
{
  "projects": [
    { "id": "p1a2b3c4d5", "slug": "ops", "name": "Ops", "description": "Deploys and backups" }
  ],
  "key": { "name": "CI", "prefix": "mcpn_Ab12Cd", "expiresAt": "2027-01-01T00:00:00.000Z" }
}
```

---

### POST /api/v1/mcp

**MCP server (also at /mcp)** — Model Context Protocol endpoint, Streamable HTTP transport, stateless (no session id), JSON responses. Supports `initialize`, `ping`, `tools/list` and `tools/call` (single messages or batches); notifications get `202`. Tools: `send_notification` {app, message, title?, url?, project?, body?, attachments?: [{filename, text | content_base64, content_type?}], attachment_ids?} and `list_projects` {}. As a shortcut, a plain `{"app":"…","message":"…"}` body (no `jsonrpc` field) sends directly, exactly like `POST /api/v1/messages`. `GET` and `DELETE` answer `405` (no server-initiated stream, no sessions).

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** A project key (`mcpn_…`).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `jsonrpc` | body | "2.0" | yes | JSON-RPC 2.0 envelope (`id`, `method`, `params`). |
| `method` | body | string | yes | `initialize`, `tools/list`, `tools/call`, `ping`. |

**Request**

```bash
curl -X POST https://mcpnotify.com/mcp \
  -H "Authorization: Bearer mcpn_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_notification","arguments":{"app":"claude-code","message":"Refactor done, tests green"}}}'
```

**Response**

```
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{ "type": "text", "text": "Sent. Ops: 3 recipients (1 in digest)" }],
    "structuredContent": { "ok": true, "recipients": 3, "messages": [ … ] }
  }
}
```

> Claude Code: `claude mcp add --transport http mcpnotify {BASE_URL}/mcp --header "Authorization: Bearer mcpn_…"`.
>
> A failed send is a tool result with `isError: true` and the reason in `content[0].text` (bad input, unknown project, rate limit).

---

### GET /api/v1/health

**Health check** — Confirms the API is up and your key is valid. Handy as a first call to verify credentials and connectivity.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key.

**Request**

```bash
curl https://mcpnotify.com/api/v1/health \
  -H "Authorization: Bearer sk_your_key_here"
```

**Response**

```
{
  "status": "healthy",
  "timestamp": "2026-07-19T12:00:00.000Z",
  "uptime": 1234.56,
  "version": "1.0.0",
  "apiKey": "My key",
  "userId": "usr_...",
  "message": "API is running successfully"
}
```

---

### GET /api/v1/stats

**Account & API usage stats** — Returns the calling user together with API-usage counters (requests today / this week / this month, error rate, API-key count).

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (scoped to the key owner).

**Request**

```bash
curl https://mcpnotify.com/api/v1/stats \
  -H "Authorization: Bearer sk_your_key_here"
```

**Response**

```
{
  "user": { "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "createdAt": "..." },
  "apiStats": {
    "totalApiKeys": 2,
    "requestsToday": 14,
    "requestsThisWeek": 98,
    "requestsThisMonth": 412,
    "errorRate": "1.20%",
    "errorCount": 5
  },
  "meta": { "timestamp": "...", "apiKey": "My key" }
}
```

---

### GET /api/v1/users

**List users** — Lists users. A regular key returns only its own user record; an admin key returns all users with pagination.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (admin keys see all users; others see themselves).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `limit` | query | integer | no | Page size, 1–100 (default 10). Admin only; ignored for non-admins. |
| `offset` | query | integer | no | Rows to skip (default 0). Admin only. |

**Request**

```bash
curl "https://mcpnotify.com/api/v1/users?limit=20&offset=0" \
  -H "Authorization: Bearer sk_your_key_here"
```

**Response**

```
{
  "users": [
    { "id": "usr_...", "email": "you@example.com", "name": "You", "role": "user", "emailVerified": null, "createdAt": "..." }
  ],
  "meta": { "limit": 20, "offset": 0, "total": 1, "apiKey": "My key" }
}
```

---

### POST /api/v1/users

**Create user (scaffold)** — Admin-only endpoint scaffold for creating a user. Ships as a stub in this starter — it validates input and echoes it back rather than persisting. Fill in real creation logic before relying on it.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Admin API keys only (others get 403).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `email` | body | string | yes | New user email. |
| `name` | body | string | yes | New user display name. |
| `role` | body | string | no | 'user' (default) or 'admin'. |

**Request**

```bash
curl -X POST https://mcpnotify.com/api/v1/users \
  -H "Authorization: Bearer sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"email":"new@example.com","name":"New User","role":"user"}'
```

**Response**

```
{
  "message": "User creation endpoint - implementation needed",
  "requestedData": { "email": "new@example.com", "name": "New User", "role": "user" },
  "apiKey": "My key"
}
```

> This is a template stub — no user is actually created yet.

---

### POST /api/v1/files

**Upload a file** — Uploads a file and stores its raw bytes. Use this instead of a form/Server Action for any real upload (Server Actions cap the body at ~1MB; this endpoint does not). Send `multipart/form-data` with a single `file` field.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (the file is owned by the key owner).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `file` | form | file | yes | The file to upload (multipart field name must be "file"). |

**Request**

```bash
curl -X POST https://mcpnotify.com/api/v1/files \
  -H "Authorization: Bearer sk_your_key_here" \
  -F "file=@./photo.png"
```

**Response**

```
{
  "id": "fil_...",
  "filename": "photo.png",
  "url": "/api/v1/files/fil_..."
}
```

> Default max size is 100MB (configurable via MAX_FILE_SIZE). Oversized uploads return 413.
>
> The returned `url` is the Bearer-gated download endpoint below.

---

### GET /api/v1/files/:id

**Download / preview a file** — Streams the raw file bytes with the stored Content-Type. Because it is Bearer-gated you cannot put it directly in an `<img src>`; fetch it with the token and build an object URL client-side.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | File id returned by the upload endpoint. |

**Request**

```bash
curl https://mcpnotify.com/api/v1/files/fil_your_file_id \
  -H "Authorization: Bearer sk_your_key_here" \
  --output downloaded-file
```

**Response**

```
Raw binary body with the stored `Content-Type` and `Content-Disposition: inline; filename="..."`. Returns `404 { "error": "File not found" }` if unknown.
```

---

### DELETE /api/v1/files/:id

**Delete a file** — Deletes a file owned by the calling key.

- **Auth:** `Authorization: Bearer sk_...` required
- **Access:** Any valid API key (only the owner may delete).

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `id` | path | string | yes | File id to delete. |

**Request**

```bash
curl -X DELETE https://mcpnotify.com/api/v1/files/fil_your_file_id \
  -H "Authorization: Bearer sk_your_key_here"
```

**Response**

```
{ "deleted": true }   // { "deleted": false } with status 404 if not found / not owned
```

---

## Notes for automated callers

- Always send the `Authorization: Bearer sk_...` header; there is no cookie/session auth here.
- On `429`/`403` with a `Retry-After` header, wait that many seconds before retrying.
- Treat any non-2xx JSON `error` field as the human-readable failure reason.
- File downloads (`GET /api/v1/files/:id`) return raw bytes, not JSON.
