API Reference
Complete reference for every HTTP endpoint exposed by Morgenruf. All API paths are relative to your APP_URL (e.g. https://app.morgenruf.dev).
Authentication
Morgenruf uses two authentication mechanisms depending on the context:
Session Authentication (Dashboard API)
The /dashboard/api/* endpoints are protected by a server-side session. Sessions are established via the OAuth install flow or by visiting /dashboard/login. The session cookie (mrn_session) is set as HttpOnly; SameSite=Lax; Secure and must be present on every request.
To authenticate programmatically, obtain a short-lived login token from the OAuth callback and exchange it at login:
GET /dashboard/login?t=<login_token>
http
The login token is a base64-encoded HMAC-signed payload (team_id + user_id + timestamp) valid for 5 minutes. It is issued automatically at the end of the OAuth flow.
Bearer Token Authentication (MCP API)
The /mcp endpoint uses Bearer token auth. Generate an API key in Dashboard β Integrations β MCP and pass it in the Authorization header:
curl -X POST https://app.morgenruf.dev/mcp \
-H "Authorization: Bearer mrn_a1b2c3d4..." \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
bash
API keys are stored as SHA-256 hashes. Only the mrn_-prefixed key shown at creation time can be used for auth β it cannot be retrieved again.
System Endpoints
GET /
Returns basic application info. No authentication required.
curl https://app.morgenruf.dev/
bash
{
"name": "morgenruf",
"version": "1.0.0",
"status": "ok"
}
json response
GET /healthz
Health check endpoint used by load balancers and Kubernetes liveness/readiness probes. Returns 200 OK when the app is healthy.
curl https://app.morgenruf.dev/healthz
bash
{"status": "ok"}
json response
OAuth Endpoints
GET /install
Redirects the browser to the Slack OAuth authorisation page. Generates an HMAC-signed state parameter automatically. No authentication required.
GET /install
β 302 Redirect to https://slack.com/oauth/v2/authorize?...
http
GET /oauth/callback
Handles the OAuth redirect from Slack. Exchanges the authorization code for a bot token, persists the installation, and redirects to the dashboard. No authentication required.
| Query param | Type | Description |
|---|---|---|
code | string | Authorization code from Slack |
state | string | HMAC-signed state token from /install |
error | string | Present if the user denied the install |
On success: 302 redirect to /dashboard?t=<login_token>.
On error: 400 with an HTML error page.
Slack Endpoints
POST /slack/events
Receives all Slack event subscriptions (messages, app mentions, app_uninstalled, etc.) and slash command payloads. Requests are verified with the SLACK_SIGNING_SECRET. This endpoint is called by Slack β you do not call it directly.
POST /slack/interactions
Receives Slack Block Kit interactive component payloads (button clicks, modal submissions, etc.). Also verified with SLACK_SIGNING_SECRET and called by Slack.
Dashboard API
All /dashboard/api/* endpoints require session authentication. They return JSON. On auth failure the response is 401 {"error": "Unauthorized"}; on missing admin role it is 403 {"error": "Admin required"}.
GET /dashboard/api/me
Returns the currently authenticated user's profile.
{
"user_id": "U012AB3CD",
"team_id": "T012AB3CD",
"team_name": "Acme Corp",
"role": "admin"
}
json response
GET /dashboard/api/standups
Lists all standup schedules configured for the workspace.
[
{
"id": "T012AB3CD",
"channel_id": "C012AB3CD",
"schedule_time": "09:00",
"schedule_tz": "America/New_York",
"schedule_days": "mon,tue,wed,thu,fri",
"questions": [
"What did you complete yesterday?",
"What are you working on today?",
"Any blockers?"
],
"active": true
}
]
json response
POST /dashboard/api/standups
Creates a new standup schedule. Admin required.
{
"channel_id": "C012AB3CD",
"schedule_time": "09:30",
"schedule_tz": "Europe/London",
"schedule_days": "mon,tue,wed,thu,fri",
"questions": ["What are you shipping today?", "Any blockers?"],
"active": true
}
json request body
Returns 201 with the created standup object.
PUT /dashboard/api/standups/<standup_id>
Updates a standup schedule. Admin required. Accepts the same fields as POST; omitted fields are left unchanged.
DELETE /dashboard/api/standups/<standup_id>
Deletes a standup schedule. Admin required. Returns {"ok": true}.
GET /dashboard/api/members
Lists workspace members. Admin required.
[
{
"user_id": "U012AB3CD",
"real_name": "Alice Smith",
"email": "alice@example.com",
"tz": "America/Chicago",
"active": true,
"role": "admin"
}
]
json response
POST /dashboard/api/members/invite
Adds a Slack user to the workspace member list. Admin required.
{ "user_id": "U012AB3CD" }
json request body
Returns 201 with the new member object. Returns 400 if user_id is missing, or 500 if the Slack API cannot be reached.
PUT /dashboard/api/members/<user_id>/role
Updates a member's role. Admin required.
{ "role": "admin" }
json request body
Valid values for role: "admin", "member". Returns {"ok": true, "user_id": "...", "role": "..."}.
GET /dashboard/api/channels
Returns a list of public Slack channels the bot has access to. Used to populate the channel selector in the dashboard.
[
{ "id": "C012AB3CD", "name": "engineering" },
{ "id": "C012AB3CE", "name": "design" }
]
json response
GET /dashboard/api/stats
Returns high-level participation statistics for the workspace.
{
"total_members": 12,
"submitted_today": 9,
"participation_7d_pct": 84,
"active_blockers": 2
}
json response
GET /dashboard/api/reports
Returns standup responses and participation data for a date range.
| Query param | Default | Description |
|---|---|---|
days | 7 | Number of days to include |
from | β | Start date YYYY-MM-DD |
to | β | End date YYYY-MM-DD |
{
"standups": [
{
"user_id": "U012AB3CD",
"standup_date": "2026-04-01",
"yesterday": "Shipped the login page.",
"today": "Working on password reset.",
"blockers": "None",
"mood": "π"
}
],
"participation": [
{ "date": "2026-04-01", "submitted": 9, "total": 12, "pct": 75 }
],
"total_days": 7
}
json response
GET /dashboard/api/analytics
Returns extended analytics including mood trends and per-member participation breakdown. Accepts the same days query param as /reports.
GET /dashboard/api/export/csv
Downloads standup data as a CSV file. Accepts optional from and to query params (YYYY-MM-DD). Returns Content-Type: text/csv with a Content-Disposition: attachment header.
Columns: standup_date, user_id, real_name, yesterday, today, blockers, mood, submitted_at.
GET /dashboard/api/webhooks
Lists registered webhook endpoints. Admin required.
[
{
"id": 1,
"url": "https://example.com/hooks/standup",
"created_at": "2026-01-15T10:23:00Z"
}
]
json response
POST /dashboard/api/webhooks
Registers a new webhook endpoint. Admin required.
{ "url": "https://example.com/hooks/standup" }
json request body
Returns 201 with the created hook object. The URL must be an https:// address; private/internal IP ranges are rejected with 400.
DELETE /dashboard/api/webhooks/<hook_id>
Removes a webhook endpoint. Admin required. Returns {"ok": true}.
GET /dashboard/api/kudos
Returns recent kudos given in the workspace.
[
{
"from_user": "U012AB3CD",
"to_user": "U012AB3CE",
"message": "Great work on the release!",
"created_at": "2026-04-01T14:00:00Z"
}
]
json response
GET /dashboard/api/kudos/leaderboard
Returns a ranked list of members by kudos received. Accepts a days query param (default: 30).
[
{ "user_id": "U012AB3CE", "real_name": "Bob Jones", "kudos_received": 7 },
{ "user_id": "U012AB3CD", "real_name": "Alice Smith", "kudos_received": 4 }
]
json response
GET /dashboard/api/templates
Returns the 25 built-in question template sets that can be applied to a standup schedule.
[
{
"name": "Classic Standup",
"questions": [
"What did you complete yesterday?",
"What are you working on today?",
"Any blockers?"
]
}
]
json response
GET /dashboard/api/schedules
Lists all standup schedules (same as /dashboard/api/standups, used by multi-schedule UI).
POST /dashboard/api/schedules
Creates a new standup schedule. Admin required. Accepts same body as POST /dashboard/api/standups.
PUT /dashboard/api/schedules/<schedule_id>
Updates a schedule. Admin required.
DELETE /dashboard/api/schedules/<schedule_id>
Deletes a schedule. Admin required. Returns {"ok": true}.
GET /dashboard/api/rules
Lists workflow automation rules for the workspace. Admin required.
[
{
"id": 1,
"trigger": "blocker_reported",
"action": "post_to_channel",
"action_target": "C012AB3CD",
"active": true
}
]
json response
POST /dashboard/api/rules
Creates a workflow automation rule. Admin required.
{
"trigger": "participation_below",
"trigger_threshold": 50,
"action": "dm_admin",
"action_target": "U012AB3CD",
"active": true
}
json request body
DELETE /dashboard/api/rules/<rule_id>
Deletes a workflow rule. Admin required. Returns {"ok": true}.
POST /dashboard/api/feed-token
Generates (or regenerates) the public standup feed token for the workspace. Admin required. Returns {"token": "abc123..."}.
DELETE /dashboard/api/feed-token
Revokes the public standup feed token. Admin required. Returns {"ok": true}.
GET /feed/<token>
Public read-only standup feed page. No authentication required β access is controlled by the token. Returns an HTML page with the most recent standup responses.
GET /dashboard/api/profile
Returns the signed-in member's own profile. Any signed-in member. The user comes from the session only.
{
"user_id": "U012AB3CD",
"birth_month": 3,
"birth_day": 14,
"start_date": "2023-03-01",
"role": "Backend engineer",
"location": "Berlin",
"ask_me_about": "Rust, bouldering",
"celebrate": true,
"set_by_admin": false,
"updated_at": "2026-09-27T10:00:00Z"
}
json response
PUT /dashboard/api/profile
Updates the signed-in member's own profile. Any signed-in member. Only the fields sent change. birth_month and birth_day go together; there is no year field, and an unknown key such as birth_year is dropped. role and location take up to 80 characters, ask_me_about up to 200.
GET /dashboard/api/profiles
Lists every current member's profile. Workspace admin required.
PUT /dashboard/api/profiles/<user_id>
Updates one member's profile. Workspace admin required. Same fields as PUT /dashboard/api/profile.
POST /dashboard/api/profiles/import
Imports birthdays and start dates from email,birthday,start_date CSV text. Workspace admin required. preview defaults to true and writes nothing; send false to save. overwrite replaces profiles members wrote themselves. See CSV import.
{ "csv": "email,birthday,start_date\npriya@example.com,03-14,2023-03-01", "preview": true, "overwrite": false }
json request body
The response counts rows as ready, unchanged, kept, unmatched and invalid, with written and a per-row list.
Celebrations endpoints
Changes need a workspace admin or a Celebrations admin. See Celebrations.
| Endpoint | Who | What it does |
|---|---|---|
GET /dashboard/api/celebrations/settings | any member | Channel, timezone, post time, birthdays, anniversaries, working days, and whether the π can be added (can_react). |
PUT /dashboard/api/celebrations/settings | admins | Saves the settings. channel_id, timezone and working_days (for example ["mon","tue","wed","thu","fri"]) are required. |
GET /dashboard/api/celebrations/holidays | any member | The holiday list. |
POST /dashboard/api/celebrations/holidays | admins | Adds {"date", "name"}, or renames the holiday on that date. |
DELETE /dashboard/api/celebrations/holidays/<date> | admins | Removes the holiday on a YYYY-MM-DD date. |
POST /dashboard/api/celebrations/holidays/import | admins | Imports date,name CSV text. preview defaults to true. |
GET /dashboard/api/celebrations/upcoming | admins | Every celebration in the next 30 days and the day it is posted. |
GET /dashboard/api/celebrations/ask-dates | admins | How many people Ask for dates would DM, and the message. Sends nothing. |
POST /dashboard/api/celebrations/ask-dates | admins | Sends the DM to people still missing dates, at most once per person per 30 days. Returns 409 until a channel and a timezone are saved. |
MCP API
The MCP (Model Context Protocol) endpoint exposes standup data to AI assistants (Claude, Cursor, GitHub Copilot). It uses JSON-RPC 2.0 over HTTP and requires a Bearer API key. See the MCP integration guide for full setup instructions.
GET /mcp
Public info endpoint. No authentication required. Returns connection metadata and the list of available tool names.
{
"name": "Morgenruf MCP Server",
"version": "1.0.0",
"transport": "http",
"endpoint": "https://app.morgenruf.dev/mcp",
"auth": "Bearer token β generate from your Morgenruf dashboard",
"docs": "https://docs.morgenruf.dev/mcp.html",
"tools": [
"get_standups",
"get_today_standups",
"get_blockers",
"get_participation",
"get_members",
"search_standups",
"get_workspace_summary",
"get_mood_summary"
]
}
json response
POST /mcp
MCP JSON-RPC 2.0 endpoint. Requires Bearer authentication.
Supported methods:
| Method | Description |
|---|---|
initialize | MCP handshake β returns protocol version and server capabilities |
tools/list | Returns all available tools with their input schemas |
tools/call | Executes a tool with the given arguments |
ping | Liveness check β returns empty result |
Available tools via tools/call:
| Tool name | Description | Key params |
|---|---|---|
get_standups | Fetch standup responses for a date range | from_date, to_date, user_id |
get_today_standups | Get all submissions from today | β |
get_blockers | Get active blockers reported by the team | days |
get_participation | Participation statistics | days |
get_members | List all workspace members | β |
search_standups | Full-text search across standup responses | query, days |
get_workspace_summary | High-level workspace health snapshot | β |
get_mood_summary | Team mood trends over time | days |
Example tools/call request:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_blockers",
"arguments": { "days": 7 }
},
"id": 1
}
json request body
{
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "[{\"user_id\": \"U012AB3CD\", \"date\": \"2026-04-01\", \"blockers\": \"Waiting on PR review\"}]"
}
]
},
"id": 1
}
json response
GET /dashboard/api/mcp/keys
Lists MCP API keys for the workspace (prefixes only β full keys are never returned after creation). Admin required.
[
{
"id": 1,
"name": "Claude Desktop",
"key_prefix": "mrn_a1b2",
"created_at": "2026-03-10T09:00:00Z",
"last_used_at": "2026-04-01T08:45:00Z",
"active": true
}
]
json response
POST /dashboard/api/mcp/keys
Generates a new MCP API key. Admin required. Optionally accepts {"name": "My Key"} in the request body.
{
"id": 2,
"name": "Cursor",
"key": "mrn_a1b2c3d4e5f6g7h8",
"key_prefix": "mrn_a1b2",
"created_at": "2026-04-05T12:00:00Z"
}
json response
The full key is only returned once at creation time. Store it securely.
DELETE /dashboard/api/mcp/keys/<key_id>
Revokes an MCP API key. Admin required. Returns {"ok": true}.
GET /dashboard/api/mcp-config
Returns ready-to-use MCP configuration snippets for Claude Desktop and Cursor.
{
"claude_desktop": {
"mcpServers": {
"morgenruf": {
"command": "npx",
"args": ["mcp-remote", "https://app.morgenruf.dev/mcp"],
"env": { "MORGENRUF_API_KEY": "mrn_..." }
}
}
}
}
json response
Error Codes
All REST endpoints use standard HTTP status codes:
| Status | Meaning |
|---|---|
200 | Success |
201 | Resource created |
302 | Redirect (OAuth flows) |
400 | Bad request β missing or invalid parameter |
401 | Unauthorized β no session or invalid Bearer token |
403 | Forbidden β authenticated but not an admin |
500 | Internal server error β check application logs |
503 | Service unavailable β database unreachable |
Error responses from the Dashboard API always include a JSON body:
{ "error": "Human-readable description of the problem" }
json
MCP JSON-RPC errors follow the JSON-RPC 2.0 error object format:
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Unauthorized β provide a valid Bearer API key from your Morgenruf dashboard"
},
"id": null
}
json
Common MCP error codes:
| Code | Meaning |
|---|---|
-32001 | Unauthorized β invalid or missing Bearer token |
-32601 | Method not found |
-32603 | Internal error β tool execution failed |