API-Leitfaden für Entwickler

So lest und schreibt ihr docket über die GraphQL-API: Listen, Filter und Seiten, Zeit buchen, Tickets anlegen, Rechnungen lesen und wie ein Fehler aussieht.

Dieser Leitfaden zeigt die Aufrufe, mit denen die meisten Automationen anfangen. Wie ihr die API einschaltet und einen Schlüssel anlegt, steht unter API und Webhooks. Jedes Beispiel wird gegen dockets Schema geprüft, sobald sich docket ändert.

Die Grundlagen

  • Eine Adresse: POST https://euerworkspace.docket.rocks/graphql mit Authorization: Bearer dkt_… und einem JSON-Body { "query": "…", "variables": { … } }.
  • Ein Schlüssel ist eine Person. Er sieht und darf, was seine Besitzerin oder sein Besitzer in der App darf: Der Schlüssel eines Projektmanagers liest Rechnungen, der eines Zeiterfassers sieht nur die eigenen Zeiten. Was die Rolle nicht erlaubt, kommt als null oder als Fehler zurück, nie als fremde Daten.
  • Die Referenz ist das Schema: GET /graphql/schema liefert es als Text, mit einer Beschreibung an jeder Abfrage, jeder Mutation und den Objekten, die sie zurückgeben. Die Beschreibungen sind englisch. Ladet es in euren GraphQL-Client, dann ergänzt er Felder von selbst.
  • IDs sind UUIDs, dieselben in der API, in Webhook-Ereignissen und in den Adressen der App.
  • Daten sind JJJJ-MM-TT in der Zeitzone eures Workspace. Dauern sind Minuten. Beträge sind netto in der Währung eures Workspace, außer ein Feld sagt brutto.

Anmelden

Für eigene Integrationen gibt es einen Weg: einen persönlichen API-Schlüssel im Header Authorization. Keine Cookies, kein Login-Aufruf, kein Token, das erneuert werden muss. Ein Schlüssel gilt, bis die Person ihn löscht oder archiviert wird oder ein Admin die API ausschaltet.

dockets eigene Programme melden sich anders an: die Web-App mit einer Sitzung nach E-Mail, Passwort und zweitem Faktor, die Mac-App mit denselben Zugangsdaten und KI-Apps über eine Zustimmung im Browser (siehe KI-Apps verbinden). Diese Wege gehören dockets Clients und können sich ohne Ankündigung ändern. Baut nicht darauf und schreibt kein Passwort in ein Skript: Ein Schlüssel lässt sich einzeln löschen und kann nie Nutzer, Einstellungen oder andere Schlüssel ändern.

Jede Abfrage und jede Mutation mit Beispielaufruf und Antwort, lesbar ohne Schlüssel, steht in der API-Referenz.

Wer bin ich

query {
  viewer {
    id
    firstname
    lastname
    role
  }
}

Listen: filtern, sortieren, blättern

Jede Liste nimmt dieselben Argumente.

query Projects($search: String, $page: Int) {
  total: projects(filter: { search: $search, archived: false }) {
    count
  }
  projects(filter: { search: $search, archived: false }, orderBy: ["title"], perPage: 50, page: $page) {
    edges {
      node {
        id
        pnr
        title
        slug
        customer {
          id
          company
        }
      }
    }
  }
}
  • Summen kommen aus einem zweiten Feld ohne perPage (oben total): Auf einer geblätterten Liste sehen count und sum nur die Seite. Fragt page 1, 2, 3 ab, bis ihr total.count Einträge habt.
  • orderBy nimmt Feldnamen, ein - davor sortiert absteigend (["-date"]).
  • Datumsfilter nehmen Vergleiche als Text: ">= 2026-10-01", "<= 2026-10-31" oder einen einzelnen Tag.
  • ID-Filter nehmen Listen, die meisten haben einen Zwilling …Not, der ausschließt.

Zeiteinträge eines Monats

query Time($project: [ID!], $dates: [String!]) {
  total: timetrackings(filter: { projectIds: $project, dates: $dates }) {
    count
    minutes: sum(field: "duration")
  }
  timetrackings(filter: { projectIds: $project, dates: $dates }, orderBy: ["date"], perPage: 100, page: 1) {
    edges {
      node {
        id
        date
        duration
        description
        user {
          id
          firstname
          lastname
        }
        service {
          id
          title
        }
        ticket {
          id
          number
          title
        }
        invoice {
          id
          number
        }
      }
    }
  }
}

Mit "dates": [">= 2026-10-01", "<= 2026-10-31"]. filter: { billing: [OPEN] } listet, was noch keine Rechnung abgerechnet hat. invoice nennt die Rechnung eines abgerechneten Eintrags (für Schlüssel, die Rechnungen sehen dürfen).

Zeit buchen

Zeit wird auf ein Ticket eines Projekts gebucht, mit einer Leistung (der Art der Arbeit) und einer Dauer in Minuten, in Schritten von 15.

mutation Book($input: CreateTimetrackingInput!) {
  createTimetracking(input: $input) {
    timetracking {
      id
      date
      duration
    }
    errors
  }
}
{
  "input": {
    "projectId": "…",
    "ticketId": "…",
    "serviceId": "…",
    "date": "2026-11-12",
    "duration": 90,
    "description": "Startseite: Hero und Navigation"
  }
}

Ohne userId gehört die Zeit der Person, der der Schlüssel gehört. Die IDs findet ihr vorher über projects, die tickets eines Projekts und services.

Ein Ticket anlegen

mutation NewTicket($input: CreateTicketInput!) {
  createTicket(input: $input) {
    ticket {
      id
      number
      title
    }
    errors
  }
}

Mit { "input": { "projectId": "…", "title": "Kontaktformular prüfen", "text": "Das Formular antwortet mit einem Fehler." } }. text ist einfacher Text, Leerzeilen trennen Absätze. Kommentare nehmen ihn auch (createTicketComment).

Rechnungen

query Invoices($year: Int) {
  invoices(filter: { year: $year, statuses: ["published", "paid"] }, orderBy: ["-date"], perPage: 50, page: 1) {
    edges {
      node {
        id
        number
        title
        status
        date
        dueDate
      }
    }
  }
}

Rechnungen lesen nur Projektmanager und Admins. Wollt ihr in dem Moment erfahren, in dem eine Rechnung veröffentlicht oder bezahlt wird, nehmt einen Webhook, statt immer wieder zu fragen.

Dateien

Wo die API eine Datei nennt (invoice { downloadUrl } an Fremdkosten, attachments { url } an einem Ticket), gibt sie einen Pfad in eurem Workspace. Ruft ihn mit demselben Schlüssel ab:

GET https://euerworkspace.docket.rocks/rails/active_storage/blobs/redirect/…/beleg.pdf
Authorization: Bearer dkt_…

Die Antwort leitet zur Datei weiter, euer Werkzeug muss also Weiterleitungen folgen. Ein Schlüssel bekommt die Dateien, die seine Person in der App öffnen darf: einen Beleg nur mit dem Schlüssel eines Projektmanagers oder Admins. Ohne Schlüssel, oder mit einem, der es nicht darf, ist die Antwort 404. Dateiabrufe zählen zu den 120 Anfragen pro Minute des Schlüssels.

Wenn etwas schiefgeht

Was ihr bekommt Was es heißt
HTTP 401 Der Schlüssel fehlt, ist falsch oder gelöscht, oder die API ist für den Workspace ausgeschaltet.
HTTP 429 Mehr als 120 Anfragen in einer Minute mit diesem Schlüssel. Wartet und versucht es erneut.
HTTP 200 mit errors ganz oben in der Antwort Die Abfrage passt nicht zum Schema, oder die Person hinter dem Schlüssel darf das nicht.
HTTP 200 mit errors in der Antwort der Mutation Der Datensatz wurde nicht gespeichert. errors nennt jedes Feld mit seinen Meldungen, wie das Formular in der App sie zeigen würde.

Eine Mutation, die geklappt hat, antwortet mit errors: null. Fragt errors immer mit ab und prüft es.

Was ein Schlüssel nicht kann

Workspace-Einstellungen, Nutzer und ihre Rollen, Verbindungen zu Jira und KI-Apps sowie API-Schlüssel und Webhooks selbst ändert ihr nur in der App. Diese Mutationen sagen das im Schema.

Verwandt: API und Webhooks · Benutzerrollen