Für Entwickler

API-Dokumentation

Hier findest du die Endpunkte, Parameter und Antwortformate für deine RunMyTime-Anbindung. Beginne mit der Authentifizierung oder springe direkt zum gewünschten Bereich.

Die Codebeispiele sind Vorlagen. Diese Seite führt keine API-Aufrufe aus.

Dein erster API-Aufruf

Erstelle ein Zugangstoken im Profil deiner RunMyTime-Anwendung und rufe anschließend dein Profil über die API ab.

GET /api/v1/me

Verwende die Adresse deiner RunMyTime-Anwendung als Basisadresse. Die Endpunkte der Kunden-API beginnen mit /api/v1. Die öffentlichen Tarifendpunkte beginnen mit /api/public und hängen an derselben Basisadresse, ohne /api/v1 davor.

https://app.runmytime.com

Die Eingabe verändert nur die angezeigten Beispiele und den kopierten Text. Es wird nichts gesendet und kein Token gespeichert.

Authentifizierung

Authentifiziere deine Aufrufe mit einem Bearer-Token. Das Token ist einem Benutzer und genau einem Unternehmen zugeordnet. Du kannst es im Profil erstellen und widerrufen, wenn der API-Zugang freigeschaltet ist.

Authorization: Bearer DEIN_API_TOKEN
Accept: application/json

Für Anfragen mit JSON-Inhalt benötigst du zusätzlich den Header Content-Type: application/json.

Das Token handelt als dieses Benutzerkonto in diesem Unternehmen. Es sieht Projekte und Projektzeiten nur so, wie diese Person sie in der Oberfläche sehen darf. Verwaltung und Administration sehen mehr als ein Mitarbeiterkonto. Ein Token öffnet nicht automatisch alle Konten und Daten des Unternehmens.

Fehlt das Token oder ist es ungültig, antwortet die API mit 401 und {"message":"Unauthenticated."}. Ein im Profil erstelltes Token hat kein Ablaufdatum. Ein widerrufenes Token ist ungültig.

Wiederholte Anfragen und Zeitangaben

Mit einem Idempotenzschlüssel kannst du dieselbe Aktion bei einem erneuten Versuch wiederholen, ohne sie doppelt auszuführen. Verwende für jede neue Aktion einen neuen Schlüssel. Bei einer Wiederholung derselben Aktion bleibt der Schlüssel unverändert.

Der Schlüssel steht im Header Idempotency-Key oder im Feld idempotency_key. Er ist optional. Erlaubt sind 8 bis 100 Zeichen aus Buchstaben, Ziffern und -_.:. Ein ungültiger Schlüssel antwortet mit 422 und code invalid_key, bevor die Aktion ausgeführt wird.

Der Schlüssel gilt für das Benutzerkonto, nicht für eine einzelne Aktion. Die erste Ausführung wird gespeichert, auch wenn sie fachlich abgelehnt wurde. Eine Wiederholung liefert diese gespeicherte Antwort und setzt replayed auf true. Die erste Antwort enthält das Feld nicht. Weicht der übrige Inhalt beim zweiten Mal ab, wird die neue Aktion nicht ausgeführt.

Die Beispielschlüssel auf dieser Seite, etwa beispiel-checkin-001, gehören zu genau einer Beispielaktion. Sie sind keine dauerhaften Schlüssel für später.

Ohne eigenen Zeitpunkt verwendet die Aktion die vorgesehene Serverzeit. Mit occurred_at übermittelst du einen Gerätezeitpunkt. Die Anwendung liest ihn als ISO-8601, übernimmt eine angegebene Zeitzonenangabe und rechnet nach UTC. Ohne Zeitzonenangabe gilt die Anwendungszeitzone UTC. Mehr als zwei Minuten in der Zukunft ergibt 422 time_in_future. Älter als 48 Stunden ergibt 422 time_too_old. Ein Zeitpunkt bis zu zwei Minuten voraus wird auf die aktuelle Serverzeit gesetzt. Ein gesetztes occurred_at ergänzt die Erfolgsantwort um time_source device und einen Hinweis mit code device_time.

Bei POST /api/v1/clock/break/end ist resume ein Boolean und standardmäßig false. true startet einen zuvor angehaltenen Projekttimer neu, wenn beim Pausenbeginn ein Timer lief und das Projekt noch buchbar ist. Sonst bleibt die Wirkung aus.

GET /api/v1/me

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft. Die Antwort beschreibt das Benutzerkonto, das Unternehmen des Tokens und die eingeschalteten Funktionen.

curl -sS -X GET "https://app.runmytime.com/api/v1/me" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/me", {
    method: "GET",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/me",
    method="GET",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "user": {"id": 12, "name": "Alex Beispiel", "email": "alex@beispiel.invalid"},
  "tenant": {"id": 4, "name": "Beispiel GmbH", "timezone": "Europe/Berlin"},
  "membership": {
    "role": "employee",
    "status": "active",
    "weekly_target_seconds": 144000,
    "workdays": [1, 2, 3, 4, 5]
  },
  "features": ["worktime", "absences", "api"]
}

Fehler. 401 ohne gültiges Token. 403 feature_missing mit feature api, wenn der API-Zugang fehlt. 403 no_membership oder tenant_inactive, wenn die Mitgliedschaft oder das Unternehmen nicht zugänglich ist.

Felder von GET /api/v1/me

Antwort
NameFormatPflichtStandardBeschreibung
user.idintegerimmer—Numerische ID des Benutzerkontos.
user.namestringimmer—Anzeigename.
user.emailstringimmer—E-Mail-Adresse des Kontos.
tenantobjectimmer—Unternehmen des Tokens: id, name, timezone. timezone ist die Zeitzone des Unternehmens, zum Beispiel Europe/Berlin.
membership.rolestringimmer—employee, team_lead, project_lead, office oder admin.
membership.statusstringimmer—Status der Mitgliedschaft, zum Beispiel active.
weekly_target_secondsintegerimmer—Wochenziel in Sekunden. 144000 entspricht 40 Stunden.
workdaysinteger[]immer—ISO-Wochentage, 1 ist Montag und 7 ist Sonntag.
featuresstring[]immer—Schlüssel der eingeschalteten Funktionen.

GET /api/v1/clock

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft. Der Aufruf liest den aktuellen Arbeitszeitstatus dieser Person. Er verändert keine Zeiten.

status ist off, wenn keine Arbeitszeit läuft, working, wenn eine Arbeitszeit läuft und keine Pause offen ist, und break während einer offenen Pause.

curl -sS -X GET "https://app.runmytime.com/api/v1/clock" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/clock", {
    method: "GET",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/clock",
    method="GET",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "server_now": "2026-10-10T14:00:00+00:00",
  "timezone": "Europe/Berlin",
  "status": "working",
  "session": {"id": 80, "started_at": "2026-10-10T06:00:00+00:00", "work_date": "2026-10-10", "net_seconds": 28800},
  "break": null,
  "timer": null,
  "today": {
    "date": "2026-10-10",
    "worked_seconds": 28800,
    "target_seconds": 28800,
    "break_seconds": 1800,
    "project_seconds": 14400,
    "unassigned_seconds": 14400
  },
  "notices": []
}

Fehler. Dieselben Zugangsfehler wie bei GET /api/v1/me.

Felder von GET /api/v1/clock

Antwort
NameFormatPflichtStandardBeschreibung
okbooleanimmer—Bei diesem Leseaufruf true.
server_nowstringimmer—Aktuelle Serverzeit als ISO-8601.
timezonestringimmer—Zeitzone des Unternehmens.
statusstringimmer—off, working oder break.
sessionobject oder nullimmer—Offene Arbeitszeit: id, started_at, work_date, net_seconds. net_seconds ist die Nettozeit in Sekunden. null, wenn status off ist.
breakobject oder nullimmer—Offene Pause: id, started_at und resume. resume ist null oder ein Objekt mit project_id und project, wenn ein Timer für die Fortsetzung gemerkt wurde.
timerobject oder nullimmer—Laufender Projekttimer: id, started_at, project_id, project, client, color, activity_id, activity, description. client, color und activity können null sein.
today.dateYYYY-MM-DDimmer—Heutiges Datum in der Unternehmenszeitzone.
today.worked_secondsintegerimmer—Gearbeitete Sekunden des Tages.
today.target_secondsintegerimmer—Soll des Tages in Sekunden.
today.break_secondsintegerimmer—Pausensekunden des Tages.
today.project_secondsintegerimmer—Projektsekunden des Tages. 0, wenn die Projektfunktion fehlt.
today.unassigned_secondsintegerimmer—Arbeitssekunden ohne Projektzuordnung.
noticesarrayimmer[]Hinweise mit code und message. Beim reinen Lesen leer.

Check-in, Pause und Check-out

Diese Aufrufe verwenden dieselben Regeln wie die Oberfläche. Idempotenz und occurred_at stehen im Abschnitt „Wiederholte Anfragen und Zeitangaben“. Die Erfolgsantwort hat denselben Aufbau wie GET /api/v1/clock. Die Beispiele zeigen die Felder, die sich durch die Aktion ändern. Zeitpunkte darin sind fiktiv. Eine abgelehnte Aktion kann den aktuellen Status im Feld state mitschicken.

POST /api/v1/clock/in

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft.

Dieser Aufruf startet die Arbeitszeit.

Request-Body
NameFormatPflichtStandardBeschreibung
occurred_atISO-8601optionalServerzeitGerätezeitpunkt. Grenzen: 48 Stunden zurück, 2 Minuten voraus.
idempotency_keystringoptional—Alternative zum Header Idempotency-Key.
curl -sS -X POST "https://app.runmytime.com/api/v1/clock/in" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-checkin-001" \
  -d '{}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-checkin-001";
  const response = await fetch(base + "/api/v1/clock/in", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-checkin-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/clock/in",
    data='{}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "working",
  "session": {"id": 81, "started_at": "2026-10-10T06:00:00+00:00", "work_date": "2026-10-10", "net_seconds": 0},
  "break": null,
  "timer": null,
  "notices": []
}

Fehler. 409 already_working, wenn bereits eine Arbeitszeit läuft. 409 out_of_order, wenn der Zeitpunkt vor dem letzten Check-out liegt. 423 period_locked, wenn der Tag in einem gesperrten Monat liegt. 403 trial_expired nach der Testphase.

POST /api/v1/clock/break/start

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft. Läuft ein Projekttimer, wird er angehalten und für resume gemerkt.

Dieser Aufruf startet eine Pause.

Request-Body
NameFormatPflichtStandardBeschreibung
occurred_atISO-8601optionalServerzeitGerätezeitpunkt. Grenzen: 48 Stunden zurück, 2 Minuten voraus.
idempotency_keystringoptional—Alternative zum Header Idempotency-Key.
curl -sS -X POST "https://app.runmytime.com/api/v1/clock/break/start" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-pause-001" \
  -d '{}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-pause-001";
  const response = await fetch(base + "/api/v1/clock/break/start", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-pause-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/clock/break/start",
    data='{}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "break",
  "notices": []
}

Fehler. 409 not_working ohne offene Arbeitszeit. 409 already_on_break, wenn schon eine Pause läuft. 409 out_of_order. 423 period_locked. 403 trial_expired.

POST /api/v1/clock/break/end

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft.

Dieser Aufruf beendet die Pause.

Request-Body
NameFormatPflichtStandardBeschreibung
occurred_atISO-8601optionalServerzeitGerätezeitpunkt. Grenzen: 48 Stunden zurück, 2 Minuten voraus.
idempotency_keystringoptional—Alternative zum Header Idempotency-Key.
resumebooleanoptionalfalsetrue setzt einen beim Pausenbeginn gemerkten Projekttimer fort, wenn das Projekt noch buchbar ist.
curl -sS -X POST "https://app.runmytime.com/api/v1/clock/break/end" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-pause-ende-001" \
  -d '{"resume":false}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-pause-ende-001";
  const response = await fetch(base + "/api/v1/clock/break/end", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{\"resume\":false}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-pause-ende-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/clock/break/end",
    data='{"resume":false}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "working",
  "break": null,
  "notices": []
}

Fehler. 409 not_on_break, wenn keine Pause läuft. 409 out_of_order. 403 trial_expired.

POST /api/v1/clock/out

Erfordert die Funktion api und ein Token mit aktiver Mitgliedschaft. Eine offene Pause und ein laufender Projekttimer werden mit beendet.

Dieser Aufruf beendet die Arbeitszeit.

Request-Body
NameFormatPflichtStandardBeschreibung
occurred_atISO-8601optionalServerzeitGerätezeitpunkt. Grenzen: 48 Stunden zurück, 2 Minuten voraus.
idempotency_keystringoptional—Alternative zum Header Idempotency-Key.
curl -sS -X POST "https://app.runmytime.com/api/v1/clock/out" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-gehen-001" \
  -d '{}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-gehen-001";
  const response = await fetch(base + "/api/v1/clock/out", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-gehen-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/clock/out",
    data='{}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "off",
  "session": null,
  "timer": null,
  "notices": []
}

Fehler. 409 not_working. 409 out_of_order. 423 period_locked. 403 trial_expired.

Projekttimer

Zusätzlich zur Funktion api ist die Funktion projects erforderlich. Ohne sie antwortet die API mit 403 feature_missing und feature projects.

Die Projekt-ID im Beispiel ist 1. Verwende die ID eines Projekts, das dieses Benutzerkonto sehen darf und auf das gebucht werden kann. Buchbar sind Projekte mit Status planned oder active, die nicht archiviert sind.

POST /api/v1/timer/start

Ohne laufende Arbeitszeit wird sie gestartet. Während einer Pause wird die Pause beendet. Läuft schon ein anderer Timer, wird er im selben Zeitpunkt beendet.

Dieser Aufruf startet einen Projekttimer.

Request-Body
NameFormatPflichtStandardBeschreibung
project_idintegerPflicht—ID eines sichtbaren, buchbaren Projekts.
activity_idintegeroptionalnullID einer aktiven Tätigkeit.
descriptionstringoptionalnullHöchstens 500 Zeichen.
occurred_atISO-8601optionalServerzeitWie bei der Arbeitszeit.
idempotency_keystringoptional—Wie bei der Arbeitszeit.
curl -sS -X POST "https://app.runmytime.com/api/v1/timer/start" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-timer-001" \
  -d '{"project_id":1}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-timer-001";
  const response = await fetch(base + "/api/v1/timer/start", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{\"project_id\":1}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-timer-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/timer/start",
    data='{"project_id":1}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "working",
  "timer": {"id": 90, "project_id": 1, "project": "Website überarbeiten", "client": "Beispiel GmbH"},
  "notices": []
}

Fehler. 404 project_not_found oder activity_not_found. 422 project_not_bookable. 409 timer_already_running, wenn derselbe Timer schon läuft. 422, wenn project_id fehlt. 403 trial_expired.

POST /api/v1/timer/stop

Beendet den laufenden Projekttimer dieser Person. Die Arbeitszeit selbst läuft weiter.

Dieser Aufruf stoppt den Projekttimer.

Request-Body
NameFormatPflichtStandardBeschreibung
occurred_atISO-8601optionalServerzeitGerätezeitpunkt. Grenzen: 48 Stunden zurück, 2 Minuten voraus.
idempotency_keystringoptional—Alternative zum Header Idempotency-Key.
curl -sS -X POST "https://app.runmytime.com/api/v1/timer/stop" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: beispiel-timer-stop-001" \
  -d '{}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  // Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
  const idempotencyKey = "beispiel-timer-stop-001";
  const response = await fetch(base + "/api/v1/timer/stop", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey
    },
    body: "{}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
# Einmal je neuer Aktion. Bei einem erneuten Versuch denselben Wert lassen.
idempotency_key = "beispiel-timer-stop-001"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
    "Idempotency-Key": idempotency_key,
}
request = urllib.request.Request(
    base + "/api/v1/timer/stop",
    data='{}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "status": "working",
  "timer": null,
  "notices": []
}

Fehler. 409 no_timer, wenn kein Timer läuft. 423 period_locked. 403 trial_expired.

GET /api/v1/projects

Erfordert api und projects. Die Liste enthält die Projekte, die dieses Benutzerkonto sehen darf. Projekte ohne besondere Zuordnung sind für Mitglieder sichtbar. Projekte mit Zuordnung nur für zugeordnete Personen, die Projektleitung und Konten mit Sicht auf alle Zeiten.

Query-Parameter
NameFormatPflichtStandardBeschreibung
pageintegeroptional1Seite der Laravel-Pagination.
per_pageintegeroptional1001 bis 200. Werte außerhalb werden auf diese Grenzen gesetzt.
include_archivedbooleanoptionalfalsetrue, 1, yes oder on zeigen archivierte Projekte. false, 0, no, off und ein leerer Wert blenden sie aus.
curl -sS -X GET "https://app.runmytime.com/api/v1/projects" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/projects", {
    method: "GET",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/projects",
    method="GET",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "data": [{
    "id": 1,
    "name": "Website überarbeiten",
    "code": "WEB-100",
    "status": "active",
    "bookable": true,
    "billable": true,
    "color": "#2563EB",
    "client": {"id": 3, "name": "Beispiel GmbH"},
    "budget_seconds": 72000,
    "archived_at": null
  }],
  "meta": {"page": 1, "per_page": 100, "total": 1}
}

Fehler. 403 feature_missing, wenn projects fehlt.

Felder eines Projekts

Objekt in data
NameFormatPflichtStandardBeschreibung
statusstringimmer—planned, active, on_hold oder completed.
bookablebooleanimmer—true nur bei planned oder active und ohne archived_at.
billablebooleanimmer—Ob das Projekt abrechenbar ist.
clientobject oder nullimmer—id und name, oder null ohne Kunde.
budget_secondsinteger oder nullimmer—Budget in Sekunden.
archived_atISO-8601 oder nullimmer—Zeitpunkt der Archivierung.

Projektzeiten

Erfordert api und projects. Die Liste zeigt nur beendete Einträge, die dieses Konto sehen darf. Mitarbeitende sehen eigene Einträge. Teamleitung sieht zusätzlich das geleitete Team. Projektleitung sieht zusätzlich Einträge auf geleiteten Projekten. Verwaltung und Administration sehen die Einträge des Unternehmens.

Im Request heißt das Datum date, in der Antwort work_date. Die Abrechenbarkeit heißt im Request is_billable und in der Antwort billable. Beginn und Ende heißen im Request start und end im Format HH:MM, in der Antwort started_at und ended_at als ISO-8601.

POST legt einen Eintrag für das Benutzerkonto des Tokens an. Es gibt dafür kein Feld user_id. PUT und DELETE verwenden dieselbe Validierung wie POST: project_id und date sind auch beim Ändern Pflicht. Es ist keine Teilaktualisierung.

Wähle ein Arbeitsdatum, das dieses Konto noch bearbeiten darf. Ohne die Berechtigung, Zeiten anderer zu ändern, gilt das Bearbeitungsfenster des Unternehmens. Die Voreinstellung ist 2 Tage, einstellbar von 0 bis 31. Ein älteres Datum antwortet mit 422 needs_correction. Ein gesperrter Monat antwortet mit 423 period_locked. Überschneidungen mit einer anderen Projektzeit derselben Person antworten mit 422 overlap. Ein Eintrag, den das Konto nicht sehen darf, antwortet mit 404.

GET /api/v1/time-entries

Liest beendete Projektzeiten. Laufende Timer fehlen, bis sie gestoppt sind.

Query-Parameter
NameFormatPflichtStandardBeschreibung
fromYYYY-MM-DDoptionalMonatsanfangBeginn. Ungültige Werte werden durch den Standard ersetzt.
toYYYY-MM-DDoptionalheuteEnde in der Unternehmenszeitzone. Liegt to vor from, werden die Werte getauscht. Mehr als 366 Tage werden auf 366 Tage ab from gekürzt.
user_idintegeroptional—Nur Einträge dieser Person, zusätzlich zur Sichtbarkeit.
client_idintegeroptional—Nur Einträge von Projekten dieses Kunden.
project_idintegeroptional—Nur Einträge dieses Projekts.
activity_idintegeroptional—Nur Einträge dieser Tätigkeit.
billablestringoptionalallall, yes oder no. Jeder andere Wert wird wie all behandelt.
pageintegeroptional1Seite der Pagination.
per_pageintegeroptional1001 bis 500.
curl -sS -X GET "https://app.runmytime.com/api/v1/time-entries" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/time-entries", {
    method: "GET",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/time-entries",
    method="GET",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "data": [{
    "id": 50,
    "user": {"id": 12, "name": "Alex Beispiel"},
    "project": {"id": 1, "name": "Website überarbeiten", "client": "Beispiel GmbH"},
    "activity": null,
    "work_date": "2026-10-10",
    "started_at": "2026-10-10T07:00:00+00:00",
    "ended_at": "2026-10-10T08:30:00+00:00",
    "duration_seconds": 5400,
    "billable": true,
    "description": null,
    "mode": "duration",
    "source": "manual"
  }],
  "meta": {"from": "2026-10-01", "to": "2026-10-10", "page": 1, "per_page": 100, "total": 1, "sum_seconds": 5400}
}

Fehler. 403 feature_missing, wenn projects fehlt. Ungültige Filter werden ersetzt oder ignoriert, nicht mit 422 abgelehnt.

Modus interval und duration

Ohne mode gilt duration, wenn duration gesetzt ist und start leer ist. Sonst gilt interval.

Bei interval sind start und end Pflicht, Format HH:MM. Datum und Uhrzeit gelten in der Zeitzone des Unternehmens und werden als UTC gespeichert. Liegt end nicht nach start, endet der Eintrag am folgenden Tag. Länger als 24 Stunden wird mit 422 too_long abgelehnt.

Bei duration ist duration Pflicht. Eine Zahl bis einschließlich 24 ist eine Stundenzahl. Eine Zahl über 24 ist eine Minutenzahl. Ein Doppelpunkt trennt Stunden und Minuten. Komma oder Punkt markieren einen Stundenbruch. Die Dauer wird an das Ende der letzten Projektzeit dieses Tages gesetzt, sonst an den Beginn der Arbeitszeit, sonst an 09:00 Uhr in der Unternehmenszeitzone.

Bedeutung von duration
NameFormatPflichtStandardBeschreibung
1:30Text——1 Stunde und 30 Minuten.
0:45Text——45 Minuten.
1,5Text——1,5 Stunden.
1.5Text——1,5 Stunden.
2Text——2 Stunden, weil 2 nicht über 24 liegt.
24Text——24 Stunden.
25Text——25 Minuten.
90Text——90 Minuten.

Erlaubt ist eine oder zwei Ziffern, optional mit :, , oder . und ein oder zwei Nachkommastellen. 100 ist als Eingabe ungültig. Eine gültige Eingabe, die länger als 24 Stunden dauert, antwortet mit 422 too_long.

POST /api/v1/time-entries

Erfolg antwortet mit 201 und ok true. data ist der gespeicherte Eintrag. Das Datum im Beispiel ersetzt du durch ein Datum im erlaubten Bearbeitungsfenster.

Dieser Aufruf erstellt einen Projektzeiteintrag.

Request-Body
NameFormatPflichtStandardBeschreibung
project_idintegerPflicht—Sichtbares Projekt. Beim Anlegen muss es buchbar sein.
dateYYYY-MM-DDPflicht—Arbeitsdatum. In der Antwort heißt das Feld work_date.
modestringoptionalsiehe Textinterval oder duration.
startHH:MMbei interval—Beginn in der Unternehmenszeitzone.
endHH:MMbei interval—Ende in der Unternehmenszeitzone.
durationstringbei duration—Siehe die Tabelle der Formate.
activity_idintegeroptionalnullTätigkeit.
descriptionstringoptionalnullHöchstens 500 Zeichen.
is_billablebooleanoptionalaus ProjektIn der Antwort billable. Ohne Angabe gilt das Projekt und, falls gesetzt, die Tätigkeit.
curl -sS -X POST "https://app.runmytime.com/api/v1/time-entries" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"project_id":1,"date":"YYYY-MM-DD","mode":"duration","duration":"1:30"}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/time-entries", {
    method: "POST",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json"
    },
    body: "{\"project_id\":1,\"date\":\"YYYY-MM-DD\",\"mode\":\"duration\",\"duration\":\"1:30\"}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/time-entries",
    data='{"project_id":1,"date":"YYYY-MM-DD","mode":"duration","duration":"1:30"}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "data": {
    "id": 51,
    "work_date": "YYYY-MM-DD",
    "duration_seconds": 5400,
    "billable": true,
    "mode": "duration",
    "source": "manual"
  }
}

Fehler. 422 mit message und errors, wenn ein Pflichtfeld fehlt. 404 project_not_found. 422 project_not_bookable, overlap, needs_correction, too_long oder invalid_time. 423 period_locked. 403 trial_expired.

PUT /api/v1/time-entries/{id}

{id} ist die ID des Eintrags. Der Body ist derselbe wie bei POST, einschließlich der Pflichtfelder. Erfolg antwortet mit 200, ok und data.

Dieser Aufruf ersetzt den Projektzeiteintrag.

Request-Body
NameFormatPflichtStandardBeschreibung
project_idintegerPflicht—Sichtbares Projekt. Beim Anlegen muss es buchbar sein.
dateYYYY-MM-DDPflicht—Arbeitsdatum. In der Antwort heißt das Feld work_date.
modestringoptionalsiehe Textinterval oder duration.
startHH:MMbei interval—Beginn in der Unternehmenszeitzone.
endHH:MMbei interval—Ende in der Unternehmenszeitzone.
durationstringbei duration—Siehe die Tabelle der Formate.
activity_idintegeroptionalnullTätigkeit.
descriptionstringoptionalnullHöchstens 500 Zeichen.
is_billablebooleanoptionalaus ProjektIn der Antwort billable. Ohne Angabe gilt das Projekt und, falls gesetzt, die Tätigkeit.
curl -sS -X PUT "https://app.runmytime.com/api/v1/time-entries/51" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"project_id":1,"date":"YYYY-MM-DD","mode":"interval","start":"09:00","end":"10:30"}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/time-entries/51", {
    method: "PUT",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json",
      "Content-Type": "application/json"
    },
    body: "{\"project_id\":1,\"date\":\"YYYY-MM-DD\",\"mode\":\"interval\",\"start\":\"09:00\",\"end\":\"10:30\"}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
    "Content-Type": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/time-entries/51",
    data='{"project_id":1,"date":"YYYY-MM-DD","mode":"interval","start":"09:00","end":"10:30"}'.encode(),
    method="PUT",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "data": {"id": 51, "mode": "interval", "billable": true}
}

Fehler. 404, wenn der Eintrag fehlt oder nicht sichtbar ist. 422 entry_running bei einem noch laufenden Timer. Dieselben Fachfehler wie bei POST. 403 forbidden, wenn die Berechtigung für fremde Zeiten fehlt. 403 trial_expired.

DELETE /api/v1/time-entries/{id}

Löscht den Eintrag. Es gibt keinen Request-Body. Erfolg ist {"ok":true}.

Dieser Aufruf löscht den Projektzeiteintrag.

curl -sS -X DELETE "https://app.runmytime.com/api/v1/time-entries/51" \
  -H "Authorization: Bearer DEIN_API_TOKEN" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const token = "DEIN_API_TOKEN";
  const response = await fetch(base + "/api/v1/time-entries/51", {
    method: "DELETE",
    headers: {
      Authorization: "Bearer " + token,
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
token = "DEIN_API_TOKEN"
headers = {
    "Authorization": "Bearer " + token,
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/v1/time-entries/51",
    method="DELETE",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true
}

Fehler. 404, wenn der Eintrag fehlt oder nicht sichtbar ist. 422 entry_running. 423 period_locked. 422 needs_correction. 403 trial_expired.

HTTP-Statuscodes und Fehler

Werte zuerst den HTTP-Status aus. Manche Antworten haben nur message. Fachliche Antworten der Zeitaktionen haben ok, code und message. Eine abgelehnte Stempel- oder Timeraktion kann zusätzlich state enthalten, den aktuellen Status wie bei GET /api/v1/clock.

Statuscodes
NameFormatPflichtStandardBeschreibung
401———Token fehlt oder ist ungültig. Körper: message. Beobachtete Antwort: {"message":"Unauthenticated."}.
403feature_missing——Funktion fehlt. Zusätzlich feature, zum Beispiel api oder projects.
403trial_expired——Schreibender Aufruf nach abgelaufener Testphase. GET bleibt möglich, solange das Unternehmen zugänglich ist.
403no_membership——Keine aktive Mitgliedschaft im Unternehmen des Tokens.
403tenant_inactive——Der Unternehmenszugang ist gesperrt.
403forbidden——Die Berechtigung für diese Zeit fehlt.
404———Eintrag nicht vorhanden oder für dieses Konto nicht sichtbar. Bei Projekten und Tätigkeiten auch project_not_found und activity_not_found.
409already_working und weitere——already_working, not_working, already_on_break, not_on_break, no_timer, timer_already_running, out_of_order.
422———Validierung mit message und errors. Fachlich: invalid_key, invalid_time, time_in_future, time_too_old, overlap, needs_correction, too_long, entry_running, project_not_bookable.
423period_locked——Der Monat des Datums ist gesperrt.
429———Anfragelimit. Körper enthält message. Header: Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining und X-RateLimit-Reset.

Beispiele für Fehlerkörper

401

{
  "message": "Unauthenticated."
}

403 feature_missing

{
  "ok": false,
  "code": "feature_missing",
  "feature": "api",
  "message": "Diese Funktion ist in Ihrem Tarif nicht enthalten."
}

409 already_working

{
  "ok": false,
  "code": "already_working",
  "message": "Sie sind bereits eingestempelt.",
  "state": {"ok": true, "status": "working"}
}

422 Validierung

{
  "message": "Projekt ist erforderlich.",
  "errors": {"project_id": ["Projekt ist erforderlich."]}
}

Die Kunden-API erlaubt 120 Anfragen pro Minute, gezählt über das Benutzerkonto des Tokens, sonst über die IP-Adresse. Fehlt das Token, kommt die 401, bevor dieses Limit zählt. Erfolgreiche Antworten können X-RateLimit-Limit und X-RateLimit-Remaining tragen. Stempelaktionen in der Weboberfläche haben ein eigenes Limit von 60 Anfragen pro Minute und zählen nicht hierher.

Öffentliche Tarife und Preisvorschau

Diese Endpunkte liefern öffentlich verfügbare Tarife und berechnen eine Preisvorschau. Sie benötigen kein Zugangstoken und gehören nicht zur API für Arbeits- und Projektzeiten. Beide liegen unter /api/public auf der Basisadresse. Das Limit ist 30 Anfragen pro Minute je IP-Adresse, getrennt von den 120 der Kunden-API.

GET /api/public/catalog

Liefert veröffentlichte öffentliche Tarife, Zusatzfunktionen und die bekannten Laufzeitkombinationen. Die Antwort setzt Cache-Control: no-store. Die Beispielantwort ist gekürzt. Die Tabelle darunter nennt die Felder.

curl -sS -X GET "https://app.runmytime.com/api/public/catalog" \
  -H "Accept: application/json"
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const response = await fetch(base + "/api/public/catalog", {
    method: "GET",
    headers: {
      Accept: "application/json"
    }
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
headers = {
    "Accept": "application/json",
}
request = urllib.request.Request(
    base + "/api/public/catalog",
    method="GET",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "generated_at": "2026-10-10T14:00:00+00:00",
  "currency": "EUR",
  "vat": {"rate_bp": 1900, "is_assumption": true},
  "self_service_checkout": false,
  "auto_convert_trial": false,
  "trial_is_self_service": true,
  "trial_rule": {"calendar_months": 1},
  "plans": [],
  "addons": [],
  "combinations": []
}

Fehler. 429, wenn das öffentliche Limit erreicht ist.

Felder von GET /api/public/catalog

Antwort
NameFormatPflichtStandardBeschreibung
plans[]objectimmer—key, name, description, audience, is_recommended, sort, self_service und versions.
versions[]objectimmer—version, billing_interval, currency, is_example_price, base_fee_cents, price_per_user_cents, included_users, min_users, setup_fee_cents, platform_fee_cents, term_months, valid_from, trial_calendar_months, trial_days, yearly_discount_bp, tiers, limits, features.
features[]objectimmer—key, name, description, category, type, is_available, limit.
addons[]objectimmer—key, name, description, benefit, unit, prerequisites, compatible_plan_keys, sort, versions.
combinations[]objectimmer—term_months, billing_interval, is_complete, true_up, reduction_timing, prepaid, plan_change_price, plan_change_features, cancellation, extension, credit_on_reduction, addon_change.
individual_offerobjectimmer—enabled, min_users, priced_enterprise_separate.
vatobjectimmer—rate_bp, is_assumption und ein Hinweistext. Beträge in der Vorschau sind Cent.

POST /api/public/quote

Der POST-Aufruf berechnet eine Vorschau. Er legt keinen Vertrag an. Preise aus dem Request werden abgelehnt. Erfolg enthält preview true.

Request-Body
NameFormatPflichtStandardBeschreibung
plan_keystringPflicht—1 bis 40 Zeichen: Kleinbuchstaben, Ziffern, Unterstrich, Bindestrich.
versionintegerPflicht—Nummer der veröffentlichten Tarifversion.
billing_intervalstringPflicht—monthly oder yearly, passend zur Version.
term_monthsintegeroptional—Derzeit 12, passend zur veröffentlichten Version.
usersintegerPflicht—0 bis 100000.
addon_keysstring[]optional[]Höchstens 20 veröffentlichte Zusatzfunktions-Schlüssel.
curl -sS -X POST "https://app.runmytime.com/api/public/quote" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"plan_key":"basic","version":2,"billing_interval":"monthly","term_months":12,"users":3}'
// Node.js 18 oder neuer, oder ein aktueller Browser.
async function callApi() {
  const base = "https://app.runmytime.com";
  const response = await fetch(base + "/api/public/quote", {
    method: "POST",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json"
    },
    body: "{\"plan_key\":\"basic\",\"version\":2,\"billing_interval\":\"monthly\",\"term_months\":12,\"users\":3}"
  });
  const payload = await response.json().catch(function () { return null; });
  if (!response.ok) {
    console.error(response.status, payload && (payload.code || payload.message), payload);
    return;
  }
  console.log(payload);
}
callApi();
import json
import urllib.error
import urllib.request

base = "https://app.runmytime.com"
headers = {
    "Accept": "application/json",
    "Content-Type": "application/json",
}
request = urllib.request.Request(
    base + "/api/public/quote",
    data='{"plan_key":"basic","version":2,"billing_interval":"monthly","term_months":12,"users":3}'.encode(),
    method="POST",
    headers=headers,
)
try:
    with urllib.request.urlopen(request) as response:
        print(response.status, json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode())

Erfolgsantwort

{
  "ok": true,
  "preview": true,
  "plan": {"key": "basic", "version": 2, "billing_interval": "monthly", "term_months": 12, "is_example_price": true, "auto_convert": false},
  "users": {"requested": 3, "effective": 3, "included": 3, "minimum": 3, "charged": 0, "minimum_applied": false},
  "lines": [{"type": "base", "label": "Grundgebühr", "amount_cents": 1900}],
  "plan_net_cents": 1900,
  "plan_vat_cents": 361,
  "plan_gross_cents": 2261,
  "setup": {"amount_cents": 0, "once": true, "in_recurring": false},
  "addons": [],
  "included_addons": [],
  "one_off": [],
  "per_user_month_cents": 450,
  "team_month_cents": 1900,
  "due_per_interval_cents": 1900,
  "trial_precedes_contract": true
}

Fehler. 422 price_override_rejected mit rejected_fields, wenn der Request ein Preisfeld enthält. 422 invalid mit field bei fehlenden oder ungültigen Pflichtfeldern. 404 plan_not_found oder version_not_found. 422 not_bookable, interval_mismatch, term_mismatch, addon_not_found oder addon_incompatible.

Umfang dieser API-Version

Abwesenheiten, Auswertungen, Leistungsnachweise, die Benutzerverwaltung und das Betreiberportal haben derzeit keine Kundenendpunkte unter /api/v1.

POST /aktion/sync gehört zur Weboberfläche, nicht zur Kunden-API.