API Documentation
General
- Base URL:
https://meine-piraten.de - Format: JSON — append
.jsonto any endpoint or sendAccept: application/json
Permission Levels
| Level | Description |
|---|---|
| Public | No authentication required |
| Any authenticated user | Valid Bearer token required |
| Admin | User with admin flag set to true |
| Superadmin | User with superadmin flag set to true |
Common Response Status Codes
| Status | Meaning |
|---|---|
200 | OK — successful GET, PATCH, PUT |
201 | Created — successful POST |
204 | No Content — successful DELETE |
401 | Unauthorized — missing or invalid token |
403 | Forbidden — insufficient permissions |
404 | Not Found — resource does not exist |
422 | Unprocessable Entity — validation errors |
Authentication
All API endpoints require authentication via a Bearer token (JWT) issued by PiratenSSO (Keycloak), unless marked as public.
Obtaining a token
Request an access token from the PiratenSSO token endpoint using the OAuth 2.0 client credentials flow:
POST https://sso.piratenpartei.de/realms/Piratenlogin/protocol/openid-connect/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
The response contains an access_token:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"expires_in": 300,
"token_type": "Bearer"
}
Using the token
Include the token in the Authorization header of every request:
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
Authentication errors
| Status | Body | Cause |
|---|---|---|
401 | {"error": "Authorization header required"} | No Authorization header sent |
401 | {"error": "Invalid token: ..."} | Token is expired, malformed, or signed by an untrusted issuer |
Example: authenticated request
curl -H "Authorization: Bearer eyJhbG..." \
-H "Accept: application/json" \
https://meine-piraten.de/tasks.json
Tasks
A task represents an actionable item assigned to an organizational entity and category.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /tasks.json | List all tasks | Any authenticated user |
| GET | /tasks/:id.json | Get a single task | Any authenticated user |
| POST | /tasks.json | Create a task | Admin |
| PATCH/PUT | /tasks/:id.json | Update a task | Any authenticated user (see field restrictions below) |
| DELETE | /tasks/:id.json | Delete a task | Admin |
Request parameters (create / update)
Wrap parameters in a task key:
{
"task": {
"title": "string (required for create, max 200 characters)",
"description": "string (optional, max 2000 characters)",
"completed": "boolean",
"creator_name": "string",
"time_needed_in_hours": "integer (optional)",
"activity_points": "integer (optional)",
"due_date": "date (YYYY-MM-DD, optional)",
"urgent": "boolean (optional)",
"category_id": "integer (required for create, foreign key)",
"entity_id": "integer (required for create, foreign key)",
"status": "string (optional, see status transitions below)",
"assignee": "string (optional, PiratenSSO username)"
}
}
Field update restrictions for non-admin users
Non-admin users may only update status and assignee. All other fields require admin privileges.
Status transitions
Task status follows a strict state machine. Invalid transitions return 422 Unprocessable Entity.
| From | Allowed transitions | Who can transition |
|---|---|---|
open | claimed | Any authenticated user |
claimed | completed, open | Any authenticated user |
completed | done, claimed | Admin only |
done | (none — terminal state) | — |
Response example (single task)
{
"id": 1,
"title": "Wahlkampfmaterial bestellen",
"description": "Flyer und Plakate für den Wahlkampf bestellen",
"completed": false,
"creator_name": "pirat42",
"time_needed_in_hours": 2,
"due_date": "2025-06-01",
"urgent": true,
"activity_points": 10,
"category_id": 1,
"entity_id": 1,
"status": "open",
"assignee": null,
"created_at": "2025-05-04T12:00:00.000Z",
"updated_at": "2025-05-04T12:00:00.000Z",
"url": "https://meine-piraten.de/tasks/1.json"
}
Comments
Comments are nested under tasks. Each comment belongs to a task and is ordered by creation date (ascending).
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /tasks/:task_id/comments.json | List comments for a task | Any authenticated user |
| POST | /tasks/:task_id/comments.json | Create a comment | Any authenticated user |
| DELETE | /tasks/:task_id/comments/:id.json | Delete a comment | Admin |
Request parameters (create)
Wrap parameters in a comment key:
{
"comment": {
"author_name": "string",
"text": "string (required)"
}
}
Response example (single comment)
{
"id": 1,
"task_id": 1,
"author_name": "pirat42",
"text": "Flyer sind bestellt.",
"created_at": "2025-05-04T12:30:00.000Z",
"updated_at": "2025-05-04T12:30:00.000Z",
"url": "https://meine-piraten.de/tasks/1/comments/1.json"
}
Messages
Private messages between users. All endpoints require JWT authentication.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /api/messages | List received messages (inbox) | Any authenticated user |
| POST | /api/messages | Send a message | Any authenticated user |
| PATCH | /api/messages/:id | Mark a message as read | Any authenticated user (own messages only) |
GET /api/messages
Returns the 50 most recent messages received by the authenticated user, ordered by newest first.
[
{
"id": 1,
"sender_id": 42,
"body": "Kannst du die Flyer bis Freitag fertig haben?",
"read": false,
"created_at": "2026-03-19T10:30:00Z"
}
]
POST /api/messages
Send a message to another user.
{
"recipient_id": 42,
"body": "Ja, kein Problem!"
}
Success response (201 Created):
{ "id": 2 }
Error (422): returned if body is blank or recipient is invalid.
PATCH /api/messages/:id
Marks a received message as read. Only the recipient can mark their own messages. Returns 200 OK with {}. Returns 404 if the message does not belong to the authenticated user.
Entities
Entities represent organizational units (Landes-, Bezirks-, or Kreisverbände). Entities can be self-referentially nested (e.g. a KV belonging to an LV).
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /entities.json | List all entities | Any authenticated user |
| GET | /entities/:id.json | Get a single entity | Any authenticated user |
| POST | /entities.json | Create an entity | Admin |
| PATCH/PUT | /entities/:id.json | Update an entity | Admin |
| DELETE | /entities/:id.json | Delete an entity | Admin |
Request parameters (create / update)
Wrap parameters in an entity key:
{
"entity": {
"name": "string (required)",
"entity_level": "string (one of: LV, BZV, KV)",
"parent_entity_id": "integer (optional, parent entity foreign key)"
}
}
Entity levels
| Value | Meaning |
|---|---|
LV | Landesverband |
BZV | Bezirksverband |
KV | Kreisverband |
Response example (single entity)
{
"id": 1,
"name": "Piratenpartei Bayern",
"entity_level": "LV",
"parent_entity_id": null,
"created_at": "2025-01-01T00:00:00.000Z",
"updated_at": "2025-01-01T00:00:00.000Z",
"parent_entity_name": null,
"url": "https://meine-piraten.de/entities/1.json"
}
Categories
Categories are used to classify tasks.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /categories.json | List all categories | Any authenticated user |
| GET | /categories/:id.json | Get a single category | Any authenticated user |
| POST | /categories.json | Create a category | Admin |
| PATCH/PUT | /categories/:id.json | Update a category | Admin |
| DELETE | /categories/:id.json | Delete a category | Admin |
Request parameters (create / update)
Wrap parameters in a category key:
{
"category": {
"name": "string (required)"
}
}
Response example (single category)
{
"id": 1,
"name": "Öffentlichkeitsarbeit",
"created_at": "2025-01-01T00:00:00.000Z",
"updated_at": "2025-01-01T00:00:00.000Z",
"url": "https://meine-piraten.de/categories/1.json"
}
News
News posts are sourced from Telegram channel updates. This endpoint is public and does not require authentication.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /api/news.json | List recent news posts | Public (no authentication required) |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Number of posts to return (max 200) |
Example request
curl -H "Accept: application/json" \
https://meine-piraten.de/api/news.json?limit=10
Response example (list)
[
{
"chat_id": -1001234567890,
"message_id": 42,
"posted_at": "2026-03-01T14:30:00Z",
"text": "Neuer Blogpost: Unsere Positionen zur Digitalpolitik..."
},
{
"chat_id": -1001234567890,
"message_id": 41,
"posted_at": "2026-02-28T10:00:00Z",
"text": "Einladung zum nächsten Stammtisch..."
}
]
Posts are ordered by posted_at descending (newest first) and limited to posts from the last 30 days.
Push Subscriptions
Register or deregister APNs device tokens for push notifications. All endpoints require JWT authentication.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| POST | /api/push_subscriptions | Register or update a device token | Any authenticated user |
| DELETE | /api/push_subscriptions/:token | Deregister a device token | Any authenticated user |
POST /api/push_subscriptions
Register a device token or update notification preferences for an existing token (upsert).
{
"token": "a1b2c3d4e5f6...",
"platform": "ios",
"messages": true,
"todos": false,
"forum": true,
"news": false
}
Success response: 200 OK with {}
Error (422): returned if validation fails.
DELETE /api/push_subscriptions/:token
Remove a device token registration. Idempotent — returns 200 OK with {} even if the token is unknown.
Notification categories
| Category | Parameter | Trigger |
|---|---|---|
| Messages | messages | New private message received |
| Todos | todos | Task assigned to you or task status changed |
| Forum | forum | New forum post (if available) |
| News | news | New Telegram news item published |
Admin Requests
Any authenticated user can request admin privileges. Requests are reviewed by superadmins. Approving a request automatically grants the user admin rights.
Endpoints
| Method | Path | Description | Permission |
|---|---|---|---|
| GET | /admin_requests/status.json | Check your own admin status | Any authenticated user |
| POST | /admin_requests.json | Submit an admin request | Any authenticated user |
| PATCH | /admin_requests/:id/approve.json | Approve a request | Superadmin |
| PATCH | /admin_requests/:id/reject.json | Reject a request | Superadmin |
| PATCH | /admin_requests/:id/demote.json | Revoke admin rights from a user | Superadmin |
Check admin status
GET /admin_requests/status.json
Authorization: Bearer eyJhbG...
Returns:
{ "admin": true }
Submit a request
Send the reason as a top-level parameter:
POST /admin_requests.json
Authorization: Bearer eyJhbG...
Content-Type: application/json
{
"reason": "Ich möchte Aufgaben für meinen KV verwalten."
}
Success response (201 Created)
{
"id": 1,
"status": "pending",
"reason": "Ich möchte Aufgaben für meinen KV verwalten.",
"created_at": "2026-02-17T10:00:00.000Z"
}
Validation error (422 Unprocessable Entity)
{
"errors": ["Reason can't be blank"]
}
Approve / Reject / Demote (superadmin only)
PATCH /admin_requests/1/approve.json
Authorization: Bearer eyJhbG...
Grants admin rights to the request's user. Returns:
{ "status": "approved" }
PATCH /admin_requests/1/reject.json
Authorization: Bearer eyJhbG...
Rejects the request without changing user permissions. Returns:
{ "status": "rejected" }
PATCH /admin_requests/1/demote.json
Authorization: Bearer eyJhbG...
Revokes admin rights from the request's user and marks the request as rejected. Returns:
{ "status": "rejected" }
Non-superadmin users receive 403 Forbidden for approve / reject / demote endpoints.