Airlog Documentation
Send messages to your phone with a single curl command. No SDK required.
Quickstart
Get your first message in under 60 seconds:
1. Sign up
Create an account at airlog.to. You'll get a channel with a unique slug.
2. Send a message
# Plain text
curl -d "Deploy completed successfully" https://airlog.to/your-slug
# JSON with level
curl -H "Content-Type: application/json" \
-d '{"text":"CPU at 95%","level":"error","title":"Server Alert"}' \
https://airlog.to/your-slug
3. Get notified
Messages appear instantly in the Airlog app. Warnings and errors trigger push notifications.
Concepts
Channels — Each channel has a unique slug URL. Think of them as separate inboxes for different apps or services.
Messages — Text or JSON payloads sent to a channel. Each has a level (info, warn, error, critical).
Levels — Control notification behavior. Info is silent, warn triggers push, error adds badge, critical adds sound.
Authentication
Ingest (sending messages)
No authentication needed. Just POST to your channel's slug URL. The slug itself acts as an API key.
REST API (managing channels)
Requires a Bearer token from OAuth login.
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
https://airlog.to/api/channels
Sign in flow:
- Redirect user to
/auth/githubor/auth/google - After OAuth, user is redirected to
/auth/callback?code=AUTH_CODE - Exchange the code for a JWT:
curl -X POST -H "Content-Type: application/json" \
-d '{"code":"AUTH_CODE"}' \
https://airlog.to/auth/token
# Response
{ "token": "eyJhbGciOi..." }
Get the authenticated user's profile.
curl -H "Authorization: Bearer TOKEN" https://airlog.to/api/me
# Response
{
"id": "6e46bcc8-...",
"email": "user@example.com",
"name": "User Name",
"avatar_url": "https://...",
"plan": "free"
}
Send Message
Send a message to a channel. Auto-detects content type.
Plain text
curl -d "Server restarted" https://airlog.to/my-app
JSON
curl -H "Content-Type: application/json" \
-d '{"text":"Disk usage 92%","level":"warn","title":"Storage"}' \
https://airlog.to/my-app
Response
{
"id": "9f05f0c6-8fdf-4350-b763-d849f0d23561",
"channel": "a1b2c3d4e5f6",
"timestamp": "2026-03-15T12:00:00Z"
}
JSON Format
| Field | Type | Description |
|---|---|---|
text required | string | Message body |
level optional | string | info, warn, error, critical (default: info) |
title optional | string | Short title for the message |
tags optional | string[] | Tags for filtering |
data optional | object | Arbitrary JSON metadata |
Message Levels
| Level | Push | Badge | Sound | Use case |
|---|---|---|---|---|
info | No | No | No | Deploy logs, status updates |
warn | Yes | No | No | High memory, slow queries |
error | Yes | Yes | No | Failed requests, exceptions |
critical | Yes | Yes | Yes | Server down, data loss |
Channels API
List all your channels. Requires auth.
# Response
[
{
"id": "5c50146a-89cd-421b-8553-53feaa506d41",
"slug": "a1b2c3d4e5f6",
"name": "my-app",
"created_at": "2026-03-15T10:00:00Z"
}
]
Create a new channel.
curl -X POST -H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"my-app"}' \
https://airlog.to/api/channels
Generate a new slug for the channel. Old slug stops working immediately.
Delete a channel and all its messages.
Members API
Invite team members to your channels with role-based access.
List all members of a channel.
Add a member to a channel.
curl -X POST -H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"user_id":"USER_ID","role":"viewer"}' \
https://airlog.to/api/channels/{id}/members
| Role | Description |
|---|---|
owner | Full control (creator) |
editor | Can manage channel settings |
viewer | Read-only access to messages |
Remove a member from a channel.
Messages API
List messages in a channel. Cursor-based pagination, newest first.
| Param | Type | Description |
|---|---|---|
limit optional | int | Max messages to return (default: 50, max: 100) |
cursor optional | string | Message ID for pagination (returns messages before this) |
Get a single message by ID.
WebSocket
Connect to receive real-time messages for a channel. Requires JWT token as query parameter:
const token = "your-jwt-token";
const channelId = "your-channel-id";
const ws = new WebSocket(
`wss://airlog.to/ws/channels/${channelId}?token=${token}`
);
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(msg.level, msg.text);
};
Messages arrive as JSON with the same format as the REST API.
MCP (AI Tools)
Airlog ships an MCP server for Claude Code, Cursor, and other AI tools.
Install
# Install the MCP binary
go install github.com/airlog/airlog/cmd/mcp@latest
# Add to Claude Code (user ID from /api/me)
claude mcp add airlog -e AIRLOG_USER_ID=YOUR_USER_ID -- airlog-mcp
# Or add to Cursor (.cursor/mcp.json)
{
"mcpServers": {
"airlog": {
"command": "airlog-mcp",
"env": { "AIRLOG_USER_ID": "YOUR_USER_ID" }
}
}
}
GET /api/me after signing in.Available Tools
| Tool | Description |
|---|---|
airlog_send | Send a message to a channel |
airlog_channels | List your channels |
airlog_history | View recent messages |
airlog_create_channel | Create a new channel |
Usage
Just tell your AI assistant: "Send deploy status to airlog" and it will use the MCP tools automatically.
Examples
Shell script (cron job monitoring)
#!/bin/bash
# backup.sh — notify on completion or failure
if pg_dump mydb > backup.sql; then
curl -d "Backup completed ($(du -h backup.sql | cut -f1))" \
https://airlog.to/ops
else
curl -H "Content-Type: application/json" \
-d '{"text":"Backup FAILED","level":"critical"}' \
https://airlog.to/ops
fi
Python
import requests
# Simple
requests.post("https://airlog.to/my-app", data="Deploy done")
# With level and metadata
requests.post("https://airlog.to/my-app", json={
"text": "Payment failed",
"level": "error",
"title": "Stripe",
"data": {"customer_id": "cus_123", "amount": 4999}
})
Node.js
// Simple
fetch("https://airlog.to/my-app", { method: "POST", body: "Deploy done" });
// With level
fetch("https://airlog.to/my-app", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: "Memory at 95%",
level: "warn",
tags: ["server-1", "memory"]
})
});
Go
package main
import (
"net/http"
"strings"
)
func main() {
http.Post("https://airlog.to/my-app",
"text/plain",
strings.NewReader("Server started"))
}
CI/CD Pipeline (GitHub Actions)
# .github/workflows/deploy.yml
- name: Notify Airlog
if: always()
run: |
if [ "${{ job.status }}" = "success" ]; then
curl -d "Deploy ${{ github.sha }} succeeded" https://airlog.to/ops
else
curl -H "Content-Type: application/json" \
-d '{"text":"Deploy failed","level":"critical","tags":["ci"]}' \
https://airlog.to/ops
fi
Rate Limits
| Plan | Messages/day | Channels | Retention |
|---|---|---|---|
| Free | 100 | 3 | 7 days |
| Pro ($9/mo) | 10,000 | Unlimited | 90 days |
| Team ($29/mo) | 50,000 | Unlimited | 1 year |
Rate limited responses return 429 Too Many Requests with headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1710547200
Response body includes reset_at timestamp (UTC).
Errors
| Code | Meaning |
|---|---|
400 | Bad request — empty body or invalid JSON |
401 | Unauthorized — missing or invalid Bearer token |
404 | Channel not found — invalid slug |
405 | Method not allowed — wrong HTTP method |
413 | Payload too large — body exceeds 64KB |
429 | Rate limited — daily quota exceeded |
500 | Internal server error |
All error responses return JSON: {"error": "description"}