API

Übersicht

Die Web-Schnittstelle von docs365 documents basiert auf einer REST-Architektur über HTTP im JSON-Format. Das bedeutet, dass jeder einzelne Endpunkt (Resource) der API eine eigene URL hat und alle Anfragen bis auf ein Authentifizierungs-Cookie zustandslos sind.

Dabei bestimmt die HTTP-Methode, welche Aktion ausgeführt wird:

  • GET liest einen Datensatz, ohne ihn zu verändern.

  • POST erzeugt einen neuen Datensatz.

  • PUT verändert einzelne Daten eines bestehenden Datensatzes.

  • DELETE löscht einen Datensatz.

Je nach Resource wird nur ein Teil der Methoden unterstützt. So wird z.B. ein DELETE verweigert, wenn Datensätze nicht gelöscht werden dürfen.

Als Zeichenkodierung wird sowohl bei Anfragen als auch Antworten UTF-8 vorausgesetzt.

Format

In der JSON-API werden oberster Ebene werden alle Daten in einem Dictionary übertragen und der Haupt-Datensatz befindet sich unter dem Schlüssel data. Häufig enthält dieser weitere verschaltete Listen und Dictionaries.

Eine komplexer JSON-Datensatz kann z.B. folgendermaßen aussehen:

{
  "data": {
    "active": true,
    "admin": false,
    "email": "max@example.com",
    "fullname": "Max Mustermann",
    "id": "00547d70-8009-3ea4-2f7c-3b8c78126ca9",
    "permissions": [
      {
        "archive_id": "0053f461-3a8c-94cc-4838-2b944a76f459",
        "permission": "write"
      },
      {
        "archive_id": "005472d9-c7e0-f3a4-6fa4-e790612a30b8",
        "permission": "read"
      }
    ],
    "roles": [
      "Archivierer",
      "Benutzer"
    ],
    "username": "max"
  }
}

multipart/form-data

Neben den JSON-Format versteht die API bei POST- und PUT-Request auch noch das von Browsern verwendete Format multipart/form-data. Verschachtelte Datensätze sind hierbei nicht möglich. Wenn Dateien hochgeladen werden, ist dieses Format aktuell notwending.

URL-Format und Versionierung

Ausgehend von der Basis-URL (in den Beispielen http://archiv.example.com/) ist die API unter dem Pfad /api/v1/ zu erreichen, wobei v1 die Versionsnummer ist:

  • http://archiv.example.com/api/v1/

Die API unterstützt unterschiedliche Versionen (aktuell nur v1) und garantiert innerhalb einer Version Rückwärtskompatibilität. Die vom Server unterstützten Version können über einen info-Request abgefragt werden:

  • JSON-API: /api/info

{
  "data": {
    "versions": [
      {
        "deprecated": false,
        "num": 1
      }
    ]
  }
}

Der Client sollte zu Beginn einer Session die vom Server unterstützen Versionen abfragen. Wenn die vom Client unterstüzte Version als deprecated markiert wurde, sollte eine Warnung ausgegeben werden. Wird die Client-Version nicht mehr vom Server unterstüzt, muss eine Fehlermeldung ausgeben werden.

Alternativ kann der Client den info-Request überspringen und sofort auf die URL der unterstützten Version zugreifen.

Wenn der Client versucht, auf eine Version zuzugreifen, die vom Server nicht mehr unterstützt wird, gibt es eine Antwort mit dem HTTP-Statuscode 410 Gone als Antwort. Auch in diesem Fall soll eine eindeutige Fehlermeldung ausgegeben werden.

Alle in den folgenden Kapiteln angegeben URLs werden relativ zu den oben genannten versionierten Basis-URLs angegeben, so bedeutet z.B. ein GET /login entweder GET http://archiv.example.com/api/v1/login.

Allgemeine API-Struktur

Die meisten API-Endpunkte unterstützen die vier HTTP-Methoden GET, POST, PUT und DELETE in folgendem Format (im nicht existierenden Beispiel-Endpunkt items):

  • GET /items

  • GET /items/<item_id>

  • POST /items

  • PUT /items/<item_id>

  • DELETE /items/<item_id>

Wenn der GET-Request ohne eine ID aufgerufen wird, wird eine Liste der Datensätze zurückgegeben, die beliebig sortiert und gefiltert werden kann. Mit ID-Parameter werden die Daten zu einem einzelnen Datensatz aufgerufen; diese Antwort ist meist detaillierter als der Listen-Datenssatz.

POST /items erzeugt einen neuen Datensatz.

Da PUT und DELETE einen bestehenden Datensatz verarbeiten, wird hier immer eine ID benötigt.

Listen

Bei Listenantworten enthält data immer eine Liste von Dictionaries:

{
  "data": [
    {
      "key": "value"
    },
    {
      "key": "value"
    }
  ],
  "total": 2
}

Limits

Standardmäßig wird die Anzahl der Elemente in der Liste auf 100 begrenzt, sodass die Antwort nicht zu groß wird. Der total-Parameter gibt die Gesamtzahl (bis zu einem konfigurierbaren Limit, aktuell 10000) an vorhandenen Datensätzen aus.

Das Limit kann durch die URL-Parameter start und limit verändert werden:

  • start gibt an, ab dem wievielten Datensatz die Liste beginnt (der ersten Datensatz hat den Index 0).

  • limit gibt die Anzahl an zu übergeben Datensätzen an.

Beispiele

  • GET /items?start=0&limit=100 ruft die ersten 100 Datensätze auf.

  • GET /items?start=100&limit=50 ruft die nächten 50 auf.

Sortierung

Die Liste kann durch die Parameter sort und direction nach jedem Feld sortiert werden. Wenn man z.B. nach _rechnungsnummer sortieren möchte, muss GET /items?sort=_rechnungsnummer aufgerufen werden. Der Parameter direction versteht die Werte ASC und DESC, wenn dieser Parameter fehlt, wird ASC angenommen. Beispielsweise soriert GET /items?sort=_rechnungsnummer&direction=DESC absteigend nach der Rechnungsnummer.

Alternativ kann die Sortierungsreihenfolge auch durch ein Leerzeichen getrennt direkt im sort-Parameter übergeben werden, in diesem Fall sind durch Komma getrennt auch mehrere Sortierungen möglich. Wenn z.B. zuerst absteigend nach _rechnungsnummer sortiert werden soll und anschließend aufsteigend nach _name, hat der sort-Parameter folgenden Inhalt:

_rechnungsnummer DESC, _name ASC

Bzw. mit URL-Encoding:

/items?sort=_rechnungsnummer%20DESC%2C%20_name%20ASC

Filterung

Alle Felder, die sortiert werden können, können auch gefiltert werden. Dafür muss der Parameter query mit dem kompletten Filter-Query übergeben werden.

Die Syntax des Filter-Querys ähnelt einem SQL-WHERE-Ausdruck. Einzelne Ausdrücke können mit AND oder OR verknüfpt werden und Klammerungen werden unterstützt.

Unterstützte Operatoren sind:

=
!=
>
>=
<
<=
in_quarter
in_year

Beispiele:

-- Rechnungsnummer ist *123456*  und der Kundenname beginnt mit "Muster"
_rechnungsnummer = 123456 AND _kunde = "Muster*"

-- Archivierungsdatum im Jahr 2014
creation_date in_year 2014

Einzelne Datensätze

Bei einzelnen Datensätzen, die durch die Angabe einer ID explizit angefragt werden, enthält data direkt ein Dictionary:

{
  "data": {
    "key": "value"
  }
}

Benutzer-Authentifizierung

Fast alle HTTP-Anfragen benötigen einen authentifizierten Benutzer. Wenn dieser nicht vorhanden ist, wird die Anfrage mit dem HTTP-Status 401 Unauthorized abgebrochen. Der Client sollte in diesem Fall möglichst transparent eine Anmeldung durchführen und die Anfrage wiederholen.

Die API kann entweder von Dienstbenutzern oder regulären Benutzer verwendet werden, deren Anmeldeprozess sich unterscheidet.

Dienstbenutzer können sich nicht in der Oberfläche anmelden, mit dem in der Administrationsoberfläche angezeigten Token aber die API direkt verwenden. Dazu muss das Token bei jedem Request in einem Cookie mit dem Namen t mitgeschickt werden.

Reguläre Benutzer haben eine Kombination von Benutzer und Passwort und müssen zunächst noch einen zusätzlichen Anmeldeschritt durchführen:

POST /login

Meldet einen Benutzer an.

Request-Parameter

username

Der Benutzername.

password

Das Passwort im Klartext.

Beispiel-Request

{
  "username": "max",
  "password": "mypassword"
}

Beispiel-Antwort

Bei einer erfolgreichen Anmeldung liefert der Server den Status 204 No Content zurück und setzt ein Cookie, das bei allen weiteren Requests mitgesendet werden muss. Bei falschen Logindaten gibt es den HTTP-Status 403 Forbidden als Antwort.

Zwei-Faktor-Authentifizierung

Wenn bei einem Benutzer Zwei-Faktor-Authentifizierung aktiviert ist, reichen Benutzername und Password alleine für die Anmeldung nicht aus, sondern es muss noch zusätzlich ein “one-time password” (OTP) übergeben werden, meistens in Form eines 6-stelligen Zahlencodes, der sich alle 30 Sekunden ändert.

Falls ein Benutzer noch einen zweiten Faktor für die Anmeldung benötigt, bricht der /login-Request mit einem Status 400 Bad Request ab. Im Body der Antwort kann der Client im "errors"-Array die Ursache ermitteln: Wenn das Feld "otp_code" die Fehlermeldung "errorRequired" enthält, muss ein Einmalpasswort übermittelt werden:

{
  "errors": [
    {
      "id": "otp_code",
      "msg": "errorRequired"
    }
  ]
}

Der Client muss anschließend den vollständigen Request mit dem zusätzlichen Feld "otp_code" (dessen Wert vom Benutzer abgefragt wird) wiederholen:

{
  "username": "max",
  "password": "mypassword",
  "otp_code": "123456"
}

Falls der eingegebene Code falsch ist, gibt es wiederum den Status 400 Bad Request mit der Fehlermeldung "errorInvalidValue":

{
  "errors": [
    {
      "id": "otp_code",
      "msg": "errorInvalidValue"
    }
  ]
}

Bei erfolgreicher Anmeldung antwortet der Server mit einem 200 OK und setzt nun zwei Cookies, die bei allen weiteren Requests mitgesendet werden müssen.

Sofern im Benutzer die Option “Vertrauenswürdige Geräte zulassen” konfiguriert ist, gibt es in der Antwort noch einen Wert "otp_token", den sich der Client auf vertrauenswürdigen Geräten dauerhaft merken kann:

{
  "data": {
    "otp_token": "5gQugwcyvs-syH4UF5JbQAV1"
  }
}

Bei zukünftigen Anmeldungen kann dann statt dem dynamisch erzeugten "otp_code" das gespeicherte "otp_token" übermittelt werden:

{
  "username": "max",
  "password": "mypassword",
  "otp_token": "5gQugwcyvs-syH4UF5JbQAV1"
}

Sollte dieses Token irgendwann einmal ungültig werden, wird in der Antwort des Servers wieder ein "errorRequired" für "otp_code" als Fehlermeldung zurückgegeben, so als hätte man kein Token übermittelt. In diesem Fall muss ein neuer Code beim Benutzer abgefragt werden.

Archive abfragen

GET /viewer_archives

Liefert eine Liste von allen Archiven und Views, auf die der aktuell angemeldete Benutzer zugreifen darf.

Beispiel-Antwort

Die folgende Antwort enthält ein reguläres Archiv und einen View.

{
  "data": [
    {
      "archive_type": "archive",
      "id": "00547da9-30f3-28c0-4402-24020c1fd8be",
      "identifier": "dokumente",
      "name": "Dokumente"
    },
    {
      "archive_type": "view",
      "id": "0053f47c-1d7d-bad0-5b5b-4f132863030b",
      "identifier": "pdf",
      "name": "PDF-Dokumente"
    }
  ],
  "total": 2
}

GET /viewer_archives/<archive_id>

Liefert Detailinformationen zu dem Archiv mit der ID <archive_id> (das Feld id aus GET /viewer_archives, es kann alternativ auch der Kurzname identifier verwendet werden).

Neben dem konfigurierten Kurznamen (identifier) und Langnamen (name), die auch in der Liste der Archiven vorhanden sind, ist die Liste der im Archiv vorhandenen Spalten (recordcolumns) die wichtigste Information.

Die für die Anzeige als Datengitter wichtigsten Informationen der recordcolumns sind:

  • name: Der Langname der Spalte, z.B. als Spaltentitel im Datengitter benötigt.

  • identifier: Der Kurzname, der insbesondere für die Übertragung der Metadaten eines Vorgangs benötigt wird.

  • columntype: Der Datentyp der Spalte. Mögliche Werte sind string, integer, float, datetime, date und boolean.

Beispiel-Antwort

{
  "data": {
    "description": null,
    "id": "00547da9-30f3-28c0-4402-24020c1fd8be",
    "identifier": "dokumente",
    "name": "Dokumente",
    "recordcolumns": [
      {
        "columntype": "string",
        "id": "00547da9-30f7-82e0-f132-b6219cd9bb90",
        "identifier": "kunde",
        "length": 0,
        "lookup_restriction": false,
        "lookup_type": "none",
        "name": "Kundenname",
        "required": false
      },
      {
        "columntype": "integer",
        "id": "00547da9-30fa-123c-cbb3-365cdfdcec3f",
        "identifier": "rechnungsnummer",
        "length": 0,
        "lookup_restriction": false,
        "lookup_type": "none",
        "name": "Rechnungs-Nummer",
        "required": false
      }
    ]
  }
}

Vorgänge abfragen

GET /records?archive_id=<archive_id>

Gibt es eine Liste von Vorgängen in dem Archiv mit der Archive-ID <archive_id> zurück. Der URL-Parameter archive_id ist immer erforderlich.

Alle Felder, die mit einem Unterstrich _ beginnen, sind die selbst konfigurierten Spalten im Archiv, dabei wird der Kurzname (identifier) der Spalte nach dem Unterstrich verwendet.

Die restlichen Felder sind immer vorhanden:

  • attachments_count: Die Anzahl der angehängten Dokumente.

  • creation_date: Der Zeitpunkt der Archivierung (im datetime-Format).

  • creator: Der Benutzer, der die Archivierung durchgeführt hat.

  • hash_id: Der unveränderliche Hash des Vorgangs.

  • id: Die in der API verwendete eindeutige ID des Vorgangs.

Beispiel-Antwort

{
  "data": [
    {
      "_kunde": "Musterfirma 1",
      "_rechnungsnummer": 123456,
      "attachments_count": 1,
      "creation_date": "2014-12-02T12:08:01.138839+00:00",
      "creator": "Max Mustermann",
      "hash_id": "6c2bcd91549c0621d86f94c0b6761c86369e6ea603a5e0635a94be2fd4f6436e",
      "id": "00547dab-a0cf-0d48-8a9d-3de79ba5fd09"
    },
    {
      "_kunde": "Musterkunde 2",
      "_rechnungsnummer": 654321,
      "attachments_count": 2,
      "creation_date": "2014-12-02T12:08:30.202461+00:00",
      "creator": "Max Mustermann",
      "hash_id": "fd105279ee06ed9cdae675af986e3812a56bd2be4b4601ab3239f6808fb7679c",
      "id": "00547dab-bddf-56d0-7b9d-228f22a5e107"
    }
  ],
  "total": 2
}

GET /records/<record_id>

Ruft einen einzelnen Vorgang mit der ID <record_id> auf.

Die Liste attachments enthält nun genaue Informationen zu den Dokumenten, die an dem Vorgang angehängt sind. Ansonsten sind die Daten identisch mit denen in der Vorgangs-Liste.

Beispiel-Antwort

{
  "data": {
    "_kunde": "Musterfirma 1",
    "_rechnungsnummer": 123456,
    "archive_id": "00547da9-30f3-28c0-4402-24020c1fd8be",
    "attachments": [
      {
        "custom_date": "2014-10-31",
        "custom_description": null,
        "custom_name": "TIF",
        "filename": "9pages.tif",
        "hash_id": "67050b052824c51bf290fafbf92ec6d980fd245e5e095f49dda6f2d7fac0c638",
        "id": "00547dab-a0d6-bb5c-55b5-f941a18f042b",
        "page_count": 9,
        "viewable": "9pages.pdf"
      }
    ],
    "creation_date": "2014-12-02T12:08:01.138839+00:00",
    "creator": "Max Mustermann",
    "hash_id": "6c2bcd91549c0621d86f94c0b6761c86369e6ea603a5e0635a94be2fd4f6436e",
    "id": "00547dab-a0cf-0d48-8a9d-3de79ba5fd09"
  }
}

Neueste Vorgangsversion ermitteln

Wenn eine neue Version eines Vorgangs erzeugt wird, bekommt die neue Version eine neue id und die alte Version wird versteckt (und ist somit nur noch für Administratoren aufrufbar). Wenn die id einer älteren Vorgangsversion bekannt ist, kann über den folgenden Endpunkt die id der neuesten Version ermittelt werden:

GET /record_version/<record_id>

Gibt die id, release_id und release_version der neuesten Version zurück, sofern die neue Version für den aufrufenden Benutzer sichtbar ist.

Beispiel-Antwort

{
    "data": {
        "id": "0065aa53-a0d9-6407-eb98-1d2d5bc010bb",
        "release_id": "0065aa53-5373-8c30-ce99-88075066a658",
        "release_version": 2
    }
}

Wenn die id in der Antwort identisch mit der <record_id> in der URL ist, ist der Vorgang weiterhin die neueste Version.

Dokumente herunterladen

GET /attachments/<attachment_id>

Lädt die Datei des Dokuments (attachment) mit der ID <attachment_id> herunter.

Vorgänge archvieren

Vorgänge bestehen im Wesentlichen aus zwei Komponenten:

  • Den angehängten Dokumenten (z.B. PDF- oder Word-Dateien).

  • Den Metadaten, die die Dokumente zusammen mit den Daten der Archivspalten zu einem Vorgang zusammenfassen.

Bei einem Archivierungsvorgang müssen immer zuerst die anzuhängenden Dateien hochgeladen werden, um sie dann anschließend bei der Archivierungsanfrage zu referenzieren. Große Dateien können vom Client in kleine Stücke (Chunks) zerteilt werden, diese werden dann beim Archvierungsvorgang wieder serverseitig zusammengesetzt. Damit sichergestellt werden kann, dass der Server die Dateien korrekt erhalten und zusammengesetzt hat, muss der Client beim Archvierungsvorgang die SHA-256-Prüfsumme der Datei mit übergeben.

POST /chunk

Lädt eine Datei hoch, die später archiviert werden soll. Dieser Endpunkt erwartet den Inhalt der Datei als Binär-Stream im Request-Body.

Beispiel-Antwort

Wichtig: Die Antworten dieses API-Endpunktes sind nicht noch einmal in data gekapselt, sondern ein Dictionary auf oberster Ebene.

{
    "id": "e5247f56-7555-472f-b5f6-5d0381be7371",
    "success": true
}

POST /upload

Lädt eine Datei hoch, die später archiviert werden soll. Dieser Endpunkt unterstützt nur multipart/form-data mit folgenden Parametern:

file

Erforderlich. Die Datei oder der Chunk, der hochgeladen werden soll.

calculate_checksum

Optional. Grundsätzlich sollte die Client die SHA-256-Prüfsumme selbst berechnen und dem Server beim Archvierungsvorgang zur Verifizierung mitteilen. Sollte dies nicht möglich sein (z.B. bei Webbrowsern), kann sich der Client den Hash auch vom Server berechnen lassen, indem calculate_checksum auf true gesetzt wird.

Beispiel-Antwort

Wichtig: Die Antworten dieses API-Endpunktes sind nicht noch einmal in data gekapselt, sondern ein Dictionary auf oberster Ebene.

{
    "checksum": "e0eb20abe0f7f3affe92cb4c0d040dfcf7b3080477e978cb2eba42508ed929b3",
    "filename": "archives.png",
    "id": "e5247f56-7555-472f-b5f6-5d0381be7371",
    "success": true
}

POST /records

Archiviert einen neuen Vorgang

Request-Parameter

archive_id

Erforderlich. Die ID des Archivs, in das der Vorgang hinzugefügt werden soll (das Feld id aus GET /viewer_archives).

files

Erforderlich. Eine Liste von Dokumenten, die angehängt werden sollen (siehe nächster Abschnitt). Die Liste kann leer sein, wenn keine Dokumente angehängt werden.

_<identifier>

Wenn Metadaten hinzugefügt werden sollen, muss als Schlüssel im Dictionary jeweils der Kurzname (identifier) mit einem vorangestellten Unterstrich verwenden werden (siehe Beispiel-Requests). Einige dieser Felder können Pflichtfelder sein.

Request-Parameter für die files-Liste
chunks

Erforderlich. Eine Liste von Chunks-IDs (das id-Feld aus POST /upload), die die Datei zusammensetzen. Diese Liste darf nicht leer sein. Wenn eine Datei im Ganzen hochgeladen wurde, ist in dieser Liste nur eine ID notwendig, ansonsten müssen die IDs in der richtigen Reihenfolge sein: Der Server setzt die hochgeladenen Chunks von links nach rechts wieder zu einer Datei zusammen.

checksum

Erforderlich. Die SHA-256-Prüfsumme der Datei. Diese wird vom Server benutzt, um zu verifizieren, dass die Datei korrekt hochgeladen wurde und bei zerteilten Dateien die Chunks wieder korrekt zusammengesetzt sind.

filename

Erforderlich. Der Dateiname des angehängten Dokumentes.

custom_name

Optional. Ein beliebig verwendbares String-Feld (maximale Länge 255).

custom_description

Optional. Ein beliebig verwendbares String-Feld (maximale Länge 2048).

custom_date

Optional. Ein beliebig verwendbares Datums-Feld.

Beispiel-Request

{
  "archive_id": "00547da9-30f3-28c0-4402-24020c1fd8be",

  "_kunde": "Musterfirma",
  "_rechnungsnummer": 123,

  "files": [{
    "chunks": ["c9b67df8-d8de-4209-8e4b-485196048bd4"],
    "checksum": "e0eb20abe0f7f3affe92cb4c0d040dfcf7b3080477e978cb2eba42508ed929b3",
    "filename": "archives.png"
  }]
}

Beispiel-Antwort

Die gleiche Antwort wie bei GET /records/<records_id>.

PUT /records/<record_id>

Verändert einen existierenden Vorgang. Es können hier sowohl die Metadaten als auch die angehängten Dokumente modifiziert werden, sofern das im Archiv erlaubt ist (beispielsweise muss ein Datenfeld als Änderbar konfiguriert sein, damit man es verändern darf). Nicht erlaubte Änderungen werden vom Server ignoriert.

Wenn revisionssicher gespeicherte Daten verändert werden, erzeugt dieser Request eine neue Version des Vorgangs.

Wenn angehängte Dokumente hinzugefügt, gelöscht oder umsortiert werden sollen, muss dafür die Liste attachments übergeben werden:

  • Jedes bereits vorhandene Dokument muss in einem eigenen Dictionary über dessen id referenziert werden.

  • Neu hochgeladene Dokumente müssen im gleichen Format wie in der files-Liste in POST /records hinzugefügt werden.

  • Die Reihenfolge in der attachments-Liste bestimmt die Reihenfolge, in der die Dokumente später in der Oberfläche angezeigt werden.

  • Alle bestehenden Dokumente, die in der attachments-Liste nicht referenziert werden, werden aus der neuen Version des Vorgangs entfernt.

Beispiel-Request

{
  "_kunde": "Musterfirma",
  "_rechnungsnummer": 123,

  "attachments": [{
    "id": "005ba9e2-885b-99fc-a951-92db3a22e238"
  },{
    "chunks": ["181ba316-309d-4df8-8728-1090295743bf"],
    "checksum": "ac4f0d97368365b30f0398d32783b7ca47c2ed6993cd1696ca8401d9d1fea31d",
    "filename": "Rechnung.pdf"
  }]
}

Beispiel-Antwort

Die gleiche Antwort wie bei GET /records/<records_id>. Wenn eine neue Version des Vorgangs erzeugt wurde, enthält die Antwort eine neue id und der Wert in release_version wird hochgezählt.