Alles, was das Dashboard kann, aus deinem Terminal
Ein Konto-Key. Liste deine Websites auf, deploye auf sie, benenne sie um, lies, was deine Formulare gesammelt haben, lösche sie — über schlichtes HTTP, aus einem Skript, einem CI-Job oder einem Agenten.
Drei Befehle
Erstelle einen Key im Dashboard, prüfe, ob er funktioniert, und stell einen Ordner online. Der Key gehört bei jedem Aufruf in einen Authorization-Header.
curl -H "Authorization: Bearer hvs_…" https://harvis.dev/api/v1
curl -H "Authorization: Bearer hvs_…" https://harvis.dev/api/v1/sites
curl -X POST https://harvis.dev/api/v1/sites \ -H "Authorization: Bearer hvs_…" \ -F "files=@index.html" -F "paths=index.html"
Web-App und API können nicht auseinanderdriften
Jede Fähigkeit wird einmal deklariert, in einer Datei, und sowohl das Dashboard als auch diese API werden daraus gebaut. openapi.json entsteht aus dieser Deklaration, und der Build schlägt fehl, wenn beide nicht übereinstimmen. Das ist also keine Dokumentation, die das Produkt beschreibt — es ist das, woraus das Produkt gemacht ist.
- Eine Implementierung pro Fähigkeit, aufgerufen von der Web-App wie von deinem Skript.
- openapi.json wird erzeugt, nie von Hand geschrieben, und unter /openapi.json ausgeliefert.
- Ein Build schlägt fehl, sobald Route, Vertrag und Dokument nicht mehr übereinstimmen.
Mit einem Key gehört dir die Website sofort
Deployen ohne Konto funktioniert weiterhin und wird es immer — du bekommst einen privaten Claim-Link, den du später öffnest. Schick einen Key beim selben Aufruf mit, und es gibt nichts zu beanspruchen: Die Website liegt vom ersten Byte an in deinem Konto und läuft nie ab.
zip -r site.zip . && curl -X POST https://harvis.dev/api/upload \ -H "Authorization: Bearer hvs_…" \ -H "Content-Type: application/zip" --data-binary @site.zip
Die ganze Oberfläche
Vollständige Request- und Response-Formen stehen im OpenAPI-Dokument.
| methode | pfad | was er tut |
|---|---|---|
| GET | /api/v1 | Check that a credential works. |
| GET | /api/v1/keys | The account's API keys. Revoked keys are not listed. |
| POST | /api/v1/keys | Create an API key. |
| DELETE | /api/v1/keys/{keyId} | Revoke an API key. Anything using it stops working immediately. |
| GET | /api/v1/me | The account a credential belongs to. |
| GET | /api/v1/sites | List the account's sites, newest first. |
| POST | /api/v1/sites | Create a site from a folder of files. |
| DELETE | /api/v1/sites/{id} | Delete a site, its files and its form submissions. |
| GET | /api/v1/sites/{id} | One site. |
| PATCH | /api/v1/sites/{id} | Rename a site, change its web address, or both. |
| POST | /api/v1/sites/{id}/deploy | Replace every file of a site with the uploaded set. |
| POST | /api/v1/sites/{id}/deploy-token | Issue a new deploy token for a site. The old one stops working immediately. |
| POST | /api/v1/sites/{id}/deploy/zip | Replace every file of a site from a zip archive sent as the raw request body. |
| GET | /api/v1/sites/{id}/files | Every file a site is serving. |
| POST | /api/v1/sites/{id}/files | Add or overwrite individual files, leaving the rest of the site alone. |
| DELETE | /api/v1/sites/{id}/submissions | Delete every submission for a site, or every one of a single form. |
| GET | /api/v1/sites/{id}/submissions | One page of a site's form submissions, newest first. |
| DELETE | /api/v1/sites/{id}/submissions/{submissionId} | Delete one submission. |
| GET | /api/v1/sites/{id}/submissions/{submissionId} | One submission. |
| GET | /api/v1/sites/{id}/submissions/csv | Export a site's form submissions as CSV. |
| POST | /api/v1/sites/{id}/submissions/read | Mark every unread submission as read. |
| GET | /api/v1/sites/{id}/submissions/summary | How many submissions a site holds, how many are unread, and which forms exist. |
| POST | /api/v1/sites/import | Create a site by downloading a single page from a known artifact URL. |
Jeder Fehler hat dieselbe Form
Verzweige über den Code, nie über die Nachricht. Die Codes sind stabil; der Fließtext ist für den, der das Log liest.
{
"error": {
"code": "subdomainTaken",
"message": "That address is already taken. Try another."
}
}