API Documentation

Simple, straightforward API for sending push notifications to your team.

Quick Start

Your API endpoint:

https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID

Replace YOUR_TEAM_ID with your actual team ID. Sign up to get started.

Example request:

await fetch("https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "New Message",
body: "Hello from the API!"
})
});

Authentication

Include your API key in the Authorization header:

Authorization: Bearer YOUR_API_KEY

Get your API keys from the API Keys page after signing up.

Request Body

Notification Content

title(string, max 200 chars) - Notification title. Required with body unless using AI to summarize raw data.
body(string, max 500 chars) - Notification message. Required with title unless using AI to summarize raw data.

Optional Fields

groups(array of strings) - Group slugs to target. Paid plans only. Example: ["engineering", "sales"]
priority(string) - Optional. Set to high for urgent alerts; omit for normal delivery.
data(object, max 10KB) - Arbitrary JSON payload. Used as the source when ai.rewrite is true and title/body are omitted.
ai(object) - Per-request AI controls, overriding your team default. Fields below.
ai.rewrite(boolean) - Turn AI on/off for this request. With title/body and a prompt it restyles them; with only data it writes the notification from raw JSON.
ai.rewrite_title(boolean, default true) - Set to false to keep your supplied title and let AI rewrite only the body. Requires a title/body request.
ai.prompt(string, max 200 chars) - AI style instruction, e.g. "make it celebratory". Sending a prompt turns AI on for the request. Required for title/body restyling unless your team default prompt is set.

Common JSON Shapes

// Minimal notification
{
"title": "New payment",
"body": "Alex paid $49.00 for the Pro plan"
}
 
// Target paid-plan groups by slug
{
"title": "Deploy failed",
"body": "Billing API deploy 842 failed during migration.",
"groups": ["engineering", "on-call"]
}
 
// Let AI write the push from raw JSON
{
"ai": {
"rewrite": true,
"prompt": "summarize as a calm incident alert"
},
"data": {
"event": "deploy.failed",
"service": "billing-api",
"environment": "production",
"deploy_id": 842,
"failed_step": "migration"
}
}
 
// Restyle title/body and keep your supplied title
{
"title": "Stripe",
"body": "4900 usd from alex@acme.co",
"ai": {
"rewrite": true,
"rewrite_title": false,
"prompt": "make the body polished, keep the amount"
}
}

AI Precedence

  1. ai.rewrite is the per-request on/off switch. Set it to true to use AI for this request, set it to false to skip AI even if your team default is enabled, or omit it to use the setting from the web UI.
  2. A request-level ai.prompt overrides the prompt saved in the web UI and, on its own, turns AI on for that request. To keep a prompt in the request but force AI off, set ai.rewrite to false — an explicit false always wins.
  3. When a request targets groups, each group can have its own AI voice set in the dashboard. Prompt precedence is your request ai.prompt › the group's voice › your team voice; a group with no voice inherits the team's. The on/off switch ai.rewriteis team-wide — groups don't have a separate toggle.
  4. With title/body, AI restyles the notification and needs either a request prompt or your saved UI prompt. With only data, AI writes the title/body from raw JSON and the prompt is optional style guidance.
  5. If you send both title and data, title/body restyling wins and the raw data is not summarized. Omit the title to summarize raw data instead.
  6. ai.rewrite_title: false only applies to title/body restyling. It preserves your supplied title and lets AI rewrite only the body.

Examples

Basic Notification

{
"title": "New Task Assigned",
"body": "You have a new task waiting for you."
}

AI Rewrite

// Restyle your own title/body with an AI prompt
{
"title": "Payment received",
"body": "4900 usd from alex@acme.co",
"ai": {
"rewrite": true,
"prompt": "make it celebratory"
}
}
 
// Keep your title and rewrite only the body
{
"title": "Stripe",
"body": "4900 usd from alex@acme.co",
"ai": {
"rewrite": true,
"rewrite_title": false,
"prompt": "make the body polished"
}
}
 
// Send raw JSON and let AI write the title/body
{
"ai": {
"rewrite": true,
"prompt": "summarize for a founder checking their phone"
},
"data": {
"type": "payment_intent.succeeded",
"amount": 4900,
"currency": "usd",
"customer": "alex@acme.co"
}
}

Paste This Into Claude Code, Codex, or Any LLM

Add Tiny Owl notifications to this app.
Public docs: https://www.tinyowl.io/docs
Use server-side code only. Store the API key in TINYOWL_API_KEY and never expose it to client-side code.
Payload shape:
type TinyOwlPayload = {
title?: string;
body?: string;
groups?: string[];
data?: Record<string, unknown>;
ai?: {
rewrite?: boolean;
rewrite_title?: boolean;
prompt?: string;
};
};
Endpoint:
POST https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
Helper:
async function sendTinyOwlNotification(payload: TinyOwlPayload) {
const response = await fetch("https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID", {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.TINYOWL_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error("Tiny Owl notification failed: " + response.status);
}
return response.json();
}
Examples:
await sendTinyOwlNotification({
title: "New payment",
body: "Alex paid $49.00 for the Pro plan"
});
await sendTinyOwlNotification({
title: "Deploy failed",
body: "Billing API deploy 842 failed during migration.",
groups: ["engineering", "on-call"],
ai: {
rewrite: true,
prompt: "short, calm incident alert with next action"
}
});
await sendTinyOwlNotification({
ai: {
rewrite: true,
prompt: "summarize for a phone push"
},
data: {
event: "checkout.completed",
amount: 4900,
customer: "alex@acme.co"
}
});
Rules:
- Use groups only when the app knows the Tiny Owl group slugs.
- Use ai.rewrite true when the payload is raw JSON or machine data that needs to
be turned into readable phone-sized copy.
- When the event already has a clear, consistent wording, send a plain
title/body and skip AI entirely.
- Use ai.rewrite_title false only when preserving a supplied title.
- Keep title/body concise for mobile push notifications.
Where to add notifications:
Look through this app for the moments its owner would actually want to know
about on their phone — new signups or payments, failed jobs or deploys, errors
and outages, support requests, and any milestone worth celebrating. Wire those
up, and skip routine noise that would be ignored.

Targeting Specific Groups

{
"title": "On-call handoff",
"body": "Primary support moved to Jamie for the next 12 hours.",
"groups": ["support", "on-call"]
}

Use the group slugs from your team groups. Groups are created and managed in the Groups page. Paid plans only.

Groups + AI Rewrite

{
"title": "DB CPU 94%",
"body": "primary-db has been above 90% CPU for 12 minutes.",
"groups": ["engineering", "on-call"],
"ai": {
"rewrite": true,
"prompt": "short, calm incident alert with the next action"
}
}

Request prompts override group voices; groups without a voice inherit your team AI voice.

Group Voice Fan-Out Response

// Included when targeted groups fan out into separate AI-voice deliveries
{
"id": "ntf_eng_123",
"status": "delivered",
"devices_reached": 7,
"attempted_devices": 7,
"title": "Deploy failed",
"body": "Billing API deploy 842 failed during migration.",
"encrypted": false,
"ai_rewritten": true,
"ai_allowance_exhausted": false,
"ai_usage": {
"used": 42,
"limit": 25000,
"resetsAt": "2026-08-01T00:00:00.000Z"
},
"groups": ["engineering", "on-call"],
"deliveries": [
{
"id": "ntf_eng_123",
"groups": ["engineering"],
"title": "Deploy failed",
"body": "Billing API deploy 842 failed during migration.",
"devices_reached": 5,
"ai_rewritten": true
},
{
"id": "ntf_oncall_456",
"groups": ["on-call"],
"title": "Heads up: deploy failed",
"body": "Billing API deploy 842 needs attention. Check the migration step.",
"devices_reached": 2,
"ai_rewritten": true
}
],
"created_at": "2026-07-26T15:30:00.000Z"
}

Response

Successful responses return a 200 status with:

{
"id": "ntf_8x7gYkL2m",
"status": "delivered",
"devices_reached": 4,
"attempted_devices": 4,
"title": "New payment",
"body": "Alex paid $49.00 for the Pro plan",
"encrypted": false,
"ai_rewritten": false,
"ai_allowance_exhausted": false,
"ai_usage": null,
"groups": null,
"created_at": "2026-07-26T15:30:00.000Z"
}

Admin Endpoints

These endpoints require a production API key (not staging). Use these to retrieve team information programmatically.

List Team Members

GET https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID/members

Returns a list of team members with their email addresses, subgroups (group slugs), and date joined.

curl -X GET "https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID/members" \
-H "Authorization: Bearer YOUR_PRODUCTION_API_KEY"

Example response:

[
{
"email": "user@example.com",
"subgroups": ["engineering", "backend"],
"joined_at": "2024-01-16T10:30:00Z"
},
{
"email": "another@example.com",
"subgroups": [],
"joined_at": "2024-01-15T08:00:00Z"
}
]

List Team Groups

GET https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID/groups

Returns a list of all subgroups (groups) in the team with their slugs, names, emojis, and member counts.

curl -X GET "https://www.tinyowl.io/api/v1/webhook/YOUR_TEAM_ID/groups" \
-H "Authorization: Bearer YOUR_PRODUCTION_API_KEY"

Example response:

[
{
"slug": "engineering",
"name": "Engineering",
"emoji": "⚙️",
"member_count": 5
},
{
"slug": "sales",
"name": "Sales",
"emoji": "💰",
"member_count": 3
}
]

Usage & Rate Limits

Usage is counted per API call, not per device or team member. If you send one notification to a team of 10 people, that counts as 1 notification toward your monthly limit, even though 10 devices receive the push.

Free: 100 notifications/month · 10 AI rewrites/month
Starter: 10,000 notifications/month · 10,000 AI rewrites/month
Pro: 100,000 notifications/month · 25,000 AI rewrites/month
Team: 1,000,000 notifications/month · 50,000 AI rewrites/month

Error Responses

401 Unauthorized

Invalid or missing API key

400 Bad Request

Invalid request body or missing required fields

403 Forbidden

API key does not belong to this team, feature requires higher tier, or admin endpoints require production API key (not staging)

429 Too Many Requests

Rate limit exceeded

Need Help?

Sign up to get started and access your API keys on the API Keys page.