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.

Tip: If your slug is compromised, use the rotate endpoint to generate a new one. Old slug immediately stops working.

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:

  1. Redirect user to /auth/github or /auth/google
  2. After OAuth, user is redirected to /auth/callback?code=AUTH_CODE
  3. Exchange the code for a JWT:
POST /auth/token
curl -X POST -H "Content-Type: application/json" \
  -d '{"code":"AUTH_CODE"}' \
  https://airlog.to/auth/token

# Response
{ "token": "eyJhbGciOi..." }
Note: Auth codes are single-use and expire in 30 seconds.
GET /api/me

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

POST /{slug}

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

FieldTypeDescription
text requiredstringMessage body
level optionalstringinfo, warn, error, critical (default: info)
title optionalstringShort title for the message
tags optionalstring[]Tags for filtering
data optionalobjectArbitrary JSON metadata

Message Levels

LevelPushBadgeSoundUse case
infoNoNoNoDeploy logs, status updates
warnYesNoNoHigh memory, slow queries
errorYesYesNoFailed requests, exceptions
criticalYesYesYesServer down, data loss

Channels API

GET /api/channels

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"
  }
]
POST /api/channels

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
POST /api/channels/{id}/rotate

Generate a new slug for the channel. Old slug stops working immediately.

DELETE /api/channels/{id}

Delete a channel and all its messages.

Members API

Invite team members to your channels with role-based access.

GET /api/channels/{id}/members

List all members of a channel.

POST /api/channels/{id}/members

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
RoleDescription
ownerFull control (creator)
editorCan manage channel settings
viewerRead-only access to messages
DELETE /api/channels/{id}/members/{userId}

Remove a member from a channel.

Messages API

GET /api/channels/{id}/messages?limit=50&cursor=MSG_ID

List messages in a channel. Cursor-based pagination, newest first.

ParamTypeDescription
limit optionalintMax messages to return (default: 50, max: 100)
cursor optionalstringMessage ID for pagination (returns messages before this)
GET /api/messages/{id}

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" }
    }
  }
}
Tip: Your user ID is available at GET /api/me after signing in.

Available Tools

ToolDescription
airlog_sendSend a message to a channel
airlog_channelsList your channels
airlog_historyView recent messages
airlog_create_channelCreate 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

PlanMessages/dayChannelsRetention
Free10037 days
Pro ($9/mo)10,000Unlimited90 days
Team ($29/mo)50,000Unlimited1 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

CodeMeaning
400Bad request — empty body or invalid JSON
401Unauthorized — missing or invalid Bearer token
404Channel not found — invalid slug
405Method not allowed — wrong HTTP method
413Payload too large — body exceeds 64KB
429Rate limited — daily quota exceeded
500Internal server error

All error responses return JSON: {"error": "description"}