CrewIO Developer Portal
Für SaaS-Builder, Agenturen, Vereine und Plattform-Teams: sichere API-Keys, starke Scopes, Webhooks und ein Endpunkt-Set für vollständige Event-Workflows.
Bearer oder x-crewio-api-key
crw_live_<keyId>_<secret>
Pro Key ein Verein
Globales API-Limit
100 req/min
Backoff + Retry wird empfohlen.
HMAC-SHA256 + Delivery Logs
Nutze die Sprache, die zu deinem Stack passt.
curl -X GET 'https://crewio.co/api/developer/v1/events?status=active&limit=20' \
-H 'Authorization: Bearer crw_live_...'
const res = await fetch('https://crewio.co/api/developer/v1/events', {
method: 'GET',
headers: {
'Authorization': 'Bearer crw_live_...'
}
})
if (!res.ok) {
const err = await res.json()
throw new Error(err.error || 'API request failed')
}
const data = await res.json()
console.log(data.events)
import requests
url = 'https://crewio.co/api/developer/v1/events'
headers = {'Authorization': 'Bearer crw_live_...'}
params = {'status': 'active', 'limit': 20}
resp = requests.get(url, headers=headers, params=params, timeout=15)
resp.raise_for_status()
payload = resp.json()
print(payload['events'])
Produktionsmuster für Web, Mobile und SaaS-Backends: CrewIO API serverseitig kapseln und sauber in den App-Flow integrieren.
Ein wiederverwendbarer CrewIO Client für dein Backend.
type CrewioRequestOptions = {
method?: 'GET' | 'POST' | 'PATCH' | 'DELETE'
body?: unknown
query?: Record<string, string | number | boolean | undefined>
}
export class CrewioClient {
constructor(
private readonly apiKey: string,
private readonly baseUrl = 'https://crewio.co/api/developer/v1'
) {}
private buildUrl(path: string, query?: CrewioRequestOptions['query']) {
const url = new URL(path, this.baseUrl + '/')
if (query) {
for (const [key, value] of Object.entries(query)) {
if (value === undefined) continue
url.searchParams.set(key, String(value))
}
}
return url.toString()
}
async request<T>(path: string, options: CrewioRequestOptions = {}): Promise<T> {
const res = await fetch(this.buildUrl(path, options.query), {
method: options.method ?? 'GET',
headers: {
'Authorization': 'Bearer ' + this.apiKey,
'Content-Type': 'application/json',
},
body: options.body ? JSON.stringify(options.body) : undefined,
cache: 'no-store',
})
const json = await res.json().catch(() => ({}))
if (!res.ok) {
throw new Error(String(json?.error || 'CrewIO API error (' + res.status + ')'))
}
return json as T
}
listEvents(status?: 'draft' | 'active' | 'archived' | 'cancelled') {
return this.request<{ events: Array<{ id: string; name: string }> }>('events', {
query: { status, limit: 50 },
})
}
createShift(eventId: string, payload: {
role_id: string
start_time: string
end_time: string
required_count?: number
allow_overbooking?: boolean
notes?: string | null
}) {
return this.request<{ shift: { id: string } }>('events/' + eventId + '/shifts', {
method: 'POST',
body: payload,
})
}
}
Frontend ruft nur deinen internen Endpunkt auf.
// app/api/app/events/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { CrewioClient } from '@/lib/crewio-client'
export const dynamic = 'force-dynamic'
const client = new CrewioClient(process.env.CREWIO_API_KEY!)
export async function GET(request: NextRequest) {
try {
const status = request.nextUrl.searchParams.get('status') || undefined
const data = await client.listEvents(
status as 'draft' | 'active' | 'archived' | 'cancelled' | undefined
)
return NextResponse.json(data, { status: 200 })
} catch (error) {
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Unknown error' },
{ status: 502 }
)
}
}
Typische Client-Einbindung für eigene Dashboards.
import { useQuery } from '@tanstack/react-query'
type EventLite = { id: string; name: string }
export function useAppEvents() {
return useQuery({
queryKey: ['app-events'],
queryFn: async () => {
const res = await fetch('/api/app/events?status=active', { credentials: 'include' })
const json = await res.json()
if (!res.ok) throw new Error(json?.error || 'Failed to load events')
return json.events as EventLite[]
},
})
}
Automatisch Clients für TS/Python erstellen.
# OpenAPI laden
curl -fsSL 'https://developers.crewio.co/api/developer/v1/openapi' -o crewio-openapi.json
# TypeScript SDK generieren
npx @openapitools/openapi-generator-cli generate \
-i crewio-openapi.json \
-g typescript-fetch \
-o src/lib/crewio-sdk
# Python SDK generieren
openapi-generator generate \
-i crewio-openapi.json \
-g python \
-o ./crewio_python_client
Starte klein mit Read-Scopes und erweitere erst bei Bedarf auf Write/Delete.
Next.js + PostgreSQL + Queue Worker + Webhooks
Python + Airflow + BigQuery/ClickHouse
Webhook Worker + Redis + Messaging APIs
n8n / Make + secure secrets + retry policy
Alle Endpunkte sind scoped und verein-isoliert.
| Method | Pfad | Scope | Beschreibung |
|---|---|---|---|
| GET | /api/developer/v1/events | events:read | Events listen (Filter, Limit). |
| POST | /api/developer/v1/events | events:create | Neues Event anlegen. |
| GET | /api/developer/v1/events/{eventId} | events:read | Ein Event lesen. |
| PATCH | /api/developer/v1/events/{eventId} | events:write | Event aktualisieren. |
| DELETE | /api/developer/v1/events/{eventId} | events:delete | Event löschen. |
| Method | Pfad | Scope | Beschreibung |
|---|---|---|---|
| GET | /api/developer/v1/events/{eventId}/roles | roles:read | Rollen laden. |
| POST | /api/developer/v1/events/{eventId}/roles | roles:write | Rolle erstellen. |
| PATCH | /api/developer/v1/events/{eventId}/roles/{roleId} | roles:write | Rolle ändern. |
| DELETE | /api/developer/v1/events/{eventId}/roles/{roleId} | roles:write | Rolle löschen. |
| GET | /api/developer/v1/events/{eventId}/shifts | shifts:read | Schichten laden. |
| POST | /api/developer/v1/events/{eventId}/shifts | shifts:write | Schicht erstellen. |
| PATCH | /api/developer/v1/events/{eventId}/shifts/{shiftId} | shifts:write | Schicht ändern. |
| DELETE | /api/developer/v1/events/{eventId}/shifts/{shiftId} | shifts:write | Schicht löschen. |
| Method | Pfad | Scope | Beschreibung |
|---|---|---|---|
| GET | /api/developer/v1/events/{eventId}/helpers | helpers:read | Event-Helfer lesen. |
| POST | /api/developer/v1/events/{eventId}/helpers | helpers:write | Helferzugriff vergeben. |
| DELETE | /api/developer/v1/events/{eventId}/helpers?userId={userId} | helpers:write | Helferzugriff entziehen. |
| GET | /api/developer/v1/events/{eventId}/messages | messages:read | Nachrichten lesen. |
| POST | /api/developer/v1/events/{eventId}/messages | messages:write | Nachricht erstellen. |
| PATCH | /api/developer/v1/events/{eventId}/messages/{messageId} | messages:write | Nachricht ändern. |
| DELETE | /api/developer/v1/events/{eventId}/messages/{messageId} | messages:write | Nachricht löschen. |
| Method | Pfad | Scope | Beschreibung |
|---|---|---|---|
| GET | /api/developer/v1/events/{eventId}/assignments | assignments:read | Assignment-Liste laden. |
| POST | /api/developer/v1/events/{eventId}/assignments | assignments:write | Assignment upserten. |
| GET | /api/developer/v1/events/{eventId}/availability/requests | availability:read | Availability-Requests lesen. |
| POST | /api/developer/v1/events/{eventId}/availability/requests | availability:write | Availability-Request erstellen. |
| GET | /api/developer/v1/events/{eventId}/availability/responses | availability:read | Responses lesen. |
| POST | /api/developer/v1/events/{eventId}/availability/responses | availability:write | Responses upserten. |
Copy-Paste-Flows basierend auf den produktiven v1 Endpunkten.
Event to Role to Shift
# 1) Event anlegen
curl -X POST 'https://crewio.co/api/developer/v1/events' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{
"name": "Sommerfest 2026",
"start_date": "2026-07-12",
"end_date": "2026-07-13",
"status": "active",
"location": "Stadtpark Mainz"
}'
# Response: { "event": { "id": "event_uuid", ... } }
# 2) Rolle anlegen
curl -X POST 'https://crewio.co/api/developer/v1/events/event_uuid/roles' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{ "name": "Getränkestand", "color": "#2563EB" }'
# 3) Schicht anlegen
curl -X POST 'https://crewio.co/api/developer/v1/events/event_uuid/shifts' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{
"role_id": "role_uuid",
"start_time": "2026-07-12T14:00:00.000Z",
"end_time": "2026-07-12T18:00:00.000Z",
"required_count": 3
}'
Shift + User status upserten
# Assignment upsert (erstellt neu oder aktualisiert bestehenden Datensatz)
curl -X POST 'https://crewio.co/api/developer/v1/events/event_uuid/assignments' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{
"shift_id": "shift_uuid",
"user_id": "user_uuid",
"state": "confirmed",
"notes": "Von externer Einsatzplanung übernommen"
}'
# Beispiel-Response:
# {
# "assignment": {
# "id": "assignment_uuid",
# "shift_id": "shift_uuid",
# "user_id": "user_uuid",
# "state": "confirmed"
# }
# }
Schichten anfragen und Antworten speichern
# 1) Anfrage mit Zeitfenstern erstellen
curl -X POST 'https://crewio.co/api/developer/v1/events/event_uuid/availability/requests' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{
"time_slots": [
{ "id": "slot_a", "label": "Sa 10:00-12:00" },
{ "id": "slot_b", "label": "Sa 12:00-14:00" }
]
}'
# 2) Antworten eines Helfers upserten
curl -X POST 'https://crewio.co/api/developer/v1/events/event_uuid/availability/responses' \
-H 'Authorization: Bearer crw_live_...' \
-H 'Content-Type: application/json' \
-d '{
"request_id": "request_uuid",
"user_id": "user_uuid",
"responses": [
{ "time_slot_id": "slot_a", "available": true },
{ "time_slot_id": "slot_b", "available": false }
]
}'
Subscriptions pro Integration auswählbar
Jedes Event enthält type, occurred_at und data.
{
"id": "evt_123...",
"type": "assignment.updated",
"occurred_at": "2026-03-13T15:35:10.000Z",
"data": {
"integration_id": "...",
"event_id": "...",
"assignment": {
"id": "...",
"shift_id": "...",
"user_id": "...",
"state": "checked_in"
}
}
}
import crypto from 'crypto'
export function verifyCrewioSignature(rawBody, signatureHeader, signingSecret) {
const provided = (signatureHeader || '').trim().replace(/^sha256=/i, '')
if (!/^[0-9a-f]{64}$/i.test(provided)) return false
const expected = crypto
.createHmac('sha256', signingSecret)
.update(rawBody, 'utf8')
.digest('hex')
const a = Buffer.from(provided, 'hex')
const b = Buffer.from(expected, 'hex')
if (a.length !== b.length) return false
return crypto.timingSafeEqual(a, b)
}
import hmac
import hashlib
def verify_crewio_signature(raw_body: bytes, signature_header: str, signing_secret: str) -> bool:
provided = (signature_header or '').replace('sha256=', '').strip().lower()
if len(provided) != 64:
return False
expected = hmac.new(
signing_secret.encode('utf-8'),
raw_body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(provided, expected)
Signatur + Timestamp + Replay-Schutz + Async-Verarbeitung
import express from 'express'
import crypto from 'crypto'
const app = express()
app.use('/webhooks/crewio', express.raw({ type: 'application/json' }))
const seen = new Map<string, number>()
const FIVE_MINUTES_MS = 5 * 60 * 1000
function verifySignature(rawBody: Buffer, signatureHeader: string, secret: string) {
const provided = (signatureHeader || '').replace(/^sha256=/i, '').trim()
if (!/^[a-f0-9]{64}$/i.test(provided)) return false
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
return crypto.timingSafeEqual(Buffer.from(provided, 'hex'), Buffer.from(expected, 'hex'))
}
app.post('/webhooks/crewio', (req, res) => {
const rawBody = req.body as Buffer
const signature = String(req.header('x-crewio-signature') || '')
const timestamp = String(req.header('x-crewio-timestamp') || '')
if (!verifySignature(rawBody, signature, process.env.CREWIO_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: 'Invalid signature' })
}
const ageMs = Math.abs(Date.now() - new Date(timestamp).getTime())
if (!timestamp || Number.isNaN(ageMs) || ageMs > FIVE_MINUTES_MS) {
return res.status(400).json({ error: 'Stale timestamp' })
}
const payload = JSON.parse(rawBody.toString('utf8')) as { id: string; type: string }
if (seen.has(payload.id)) {
return res.status(200).json({ ok: true, duplicate: true })
}
seen.set(payload.id, Date.now())
// Job in Queue legen (BullMQ/SQS/Rabbit), nicht im Request blockieren.
console.log('CrewIO event', payload.type, payload.id)
return res.status(200).json({ ok: true })
})
| Status | Bedeutung | Empfohlene Reaktion |
|---|---|---|
| 400 | Payload/Params ungültig | Felder gegen API-Validierung prüfen, Datums-/UUID-Format korrigieren. |
| 401 | Key fehlt/ungültig/abgelaufen/revoked | Token rotieren, Ablaufdatum/Revocation prüfen. |
| 403 | Scope fehlt oder Verein-Fremdzugriff | Scopes der Integration erweitern und event/verein-Zuordnung checken. |
| 404 | Ressource existiert nicht | ID prüfen und sicherstellen, dass sie zum Ziel-Event gehört. |
| 429 | Rate limit erreicht | Exponential Backoff + Retry-Jitter + Queueing verwenden. |
| 500 | Server/DB-Fehler | Retry mit Circuit Breaker; Fehler korrelieren und Support kontaktieren. |