Hermiis REST API
Integrate Hermiis into your CI/CD pipelines, internal tools, and automations. Read and write workspaces, boards, tasks, and features using standard JSON over HTTPS.
Secure
HTTPS-only, Bearer token auth, SHA-256 hashed token storage.
Fast
JSON responses. Up to 300 req/5 min per token.
OpenAPI
Machine-readable spec at /openapi.json.
Base URL
https://hermiis.com/api/v1
Authentication
The API uses Personal Access Tokens (PAT). Generate one in your account: Settings → API Tokens.
Include the token in every request as a
Bearer
token in the
Authorization
header:
Authorization: Bearer hrm_at_xxxxxxxxxxxxxxxxxxxx
curl -X GET https://hermiis.com/api/v1/workspaces \
-H "Authorization: Bearer hrm_at_xxxxxxxxxxxxxxxxxxxx"
Errors
All errors return a JSON body with an
error
key (or
errors
for validation failures).
| Status | Meaning | Body |
|---|---|---|
| 200 OK | Request succeeded | {"resource": {…}} |
| 201 Created | Resource created | {"resource": {…}} |
| 401 Unauthorized | Missing or invalid token | {"error": "Unauthorized"} |
| 403 Forbidden | Not a workspace member | {"error": "Forbidden"} |
| 404 Not Found | Resource not found | {"error": "Not found"} |
| 422 Unprocessable Entity | Validation failed | {"errors": {"title": ["can't be blank"]}} |
| 429 Too Many Requests | Rate limit exceeded | {"error": "Too many requests"} |
Rate Limiting
Each API token is limited to 300 requests per 5-minute window.
When the limit is exceeded the server returns 429 Too Many Requests.
Workspaces
/workspaces
List workspaces
Returns all workspaces the authenticated user belongs to.
Response Fields
| Field | Type | Description |
|---|---|---|
id
|
string (ULID) | Unique identifier |
name
|
string | Display name |
slug
|
string | URL-safe slug used in all API paths |
inserted_at
|
ISO 8601 | Creation timestamp |
Example Request
curl https://hermiis.com/api/v1/workspaces \
-H "Authorization: Bearer $TOKEN"
Example Response
{
"workspaces": [
{
"id": "01HRXYZ123ABCDEFGH456789",
"name": "Acme Engineering",
"slug": "acme",
"inserted_at": "2025-01-15T10:23:00Z"
}
]
}
Boards
/workspaces/:workspace_slug/boards
List boards
Returns all boards in a workspace. Must be a workspace member.
Path Parameters
| Name | Type | Description |
|---|---|---|
workspace_slug
*
|
string | Workspace URL slug (e.g. acme) |
Response Fields
| Field | Type | Description |
|---|---|---|
id
|
string (ULID) | Unique identifier |
name
|
string | Board display name |
slug
|
string | URL-safe board slug |
inserted_at
|
ISO 8601 | Creation timestamp |
Example Request
curl https://hermiis.com/api/v1/workspaces/acme/boards \
-H "Authorization: Bearer $TOKEN"
Example Response
{
"boards": [
{"id": "01HRYYYY456XYZABCDEF1234", "name": "Main Track", "slug": "mt", "inserted_at": "2025-01-20T08:00:00Z"},
{"id": "01HRBBBB789", "name": "Design System", "slug": "ds", "inserted_at": "2025-01-22T12:30:00Z"}
]
}
Tasks
/workspaces/:workspace_slug/boards/:board_slug/tasks
List tasks
Returns all tasks on a board.
Path Parameters
| Name | Type | Description |
|---|---|---|
workspace_slug
*
|
string | Workspace slug |
board_slug
*
|
string | Board slug |
Response Fields
| Field | Type | Description |
|---|---|---|
id
|
string (ULID) | Task ULID — use this in PATCH URLs |
title
|
string | Task title |
description
|
string | null | Markdown body |
priority
|
urgent|high|medium|low|null | Priority level |
estimated_hours
|
number | null | Estimated hours of effort |
inserted_at
|
ISO 8601 | Creation timestamp |
updated_at
|
ISO 8601 | Last updated timestamp |
Example Request
curl https://hermiis.com/api/v1/workspaces/acme/boards/mt/tasks \
-H "Authorization: Bearer $TOKEN"
Example Response
{
"tasks": [
{
"id": "01HRZZZZ789ABCDEFGH12345",
"title": "Implement OAuth2 login",
"description": "Add Google and GitHub SSO.",
"priority": "high",
"estimated_hours": 8,
"inserted_at": "2025-02-01T09:30:00Z",
"updated_at": "2025-02-03T14:15:00Z"
}
]
}
/workspaces/:workspace_slug/boards/:board_slug/tasks
Create a task
Creates a new task. Placed in the first status column of the board.
Path Parameters
| Name | Type | Description |
|---|---|---|
workspace_slug
*
|
string | Workspace slug |
board_slug
*
|
string | Board slug |
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
title
*
|
string | Task title |
description_markdown
|
string | Markdown body (optional) |
priority
|
urgent|high|medium|low | Priority (optional) |
estimated_hours
|
number | Estimated hours (optional) |
Example Request
curl -X POST https://hermiis.com/api/v1/workspaces/acme/boards/mt/tasks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Fix null pointer", "priority": "urgent", "estimated_hours": 2}'
Example Response
{
"task": {
"id": "01HRNEWW111ABCDEFGH99999",
"title": "Fix null pointer",
"description": null,
"priority": "urgent",
"estimated_hours": 2,
"inserted_at": "2025-04-28T16:00:00Z",
"updated_at": "2025-04-28T16:00:00Z"
}
}
/workspaces/:workspace_slug/boards/:board_slug/tasks/:task_id
Update a task
Partial update — only fields you provide are changed. Omit fields you don't want to modify.
Path Parameters
| Name | Type | Description |
|---|---|---|
workspace_slug
*
|
string | Workspace slug |
board_slug
*
|
string | Board slug |
task_id
*
|
string (ULID) | Task ULID from the id field |
Request Body (JSON)
| Field | Type | Description |
|---|---|---|
title
|
string | New title (optional) |
description_markdown
|
string | New Markdown body (optional) |
priority
|
urgent|high|medium|low|null | Priority level (optional) |
estimated_hours
|
number|null | Estimated hours (optional) |
Example Request
curl -X PATCH \
https://hermiis.com/api/v1/workspaces/acme/boards/mt/tasks/01HRZZZZ789ABCDEFGH12345 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"priority": "low", "estimated_hours": 4}'
Example Response
{
"task": {
"id": "01HRZZZZ789ABCDEFGH12345",
"title": "Implement OAuth2 login",
"description": "Add Google and GitHub SSO.",
"priority": "low",
"estimated_hours": 4,
"inserted_at": "2025-02-01T09:30:00Z",
"updated_at": "2025-04-28T16:05:00Z"
}
}
Features
/workspaces/:workspace_slug/boards/:board_slug/features
List features
Returns all features (epics) on a board. Features group related tasks.
Path Parameters
| Name | Type | Description |
|---|---|---|
workspace_slug
*
|
string | Workspace slug |
board_slug
*
|
string | Board slug |
Response Fields
| Field | Type | Description |
|---|---|---|
id
|
string (ULID) | Feature ULID |
title
|
string | Feature title |
status
|
draft|in_progress|done|archived | Lifecycle status |
inserted_at
|
ISO 8601 | Creation timestamp |
Example Request
curl https://hermiis.com/api/v1/workspaces/acme/boards/mt/features \
-H "Authorization: Bearer $TOKEN"
Example Response
{
"features": [
{
"id": "01HRAAAA000ABCDEFGH56789",
"title": "Auth & Identity",
"status": "in_progress",
"inserted_at": "2025-01-22T11:00:00Z"
}
]
}
Code Examples
# List workspaces
curl https://hermiis.com/api/v1/workspaces \
-H "Authorization: Bearer $TOKEN"
# Create a task
curl -X POST https://hermiis.com/api/v1/workspaces/acme/boards/mt/tasks \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Deploy to production", "priority": "high"}'
token = System.get_env("HERMIIS_API_TOKEN")
base = "https://hermiis.com/api/v1"
hdrs = [{"authorization", "Bearer #{token}"}]
# List workspaces
{:ok, %{body: body}} =
Req.get("#{base}/workspaces", headers: hdrs)
# Create a task
{:ok, %{status: 201, body: %{"task" => task}}} =
Req.post("#{base}/workspaces/acme/boards/mt/tasks",
headers: hdrs,
json: %{title: "Deploy to production", priority: "high"}
)
import httpx, os
TOKEN = os.environ["HERMIIS_API_TOKEN"]
BASE = "https://hermiis.com/api/v1"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
# List tasks
tasks = httpx.get(f"{BASE}/workspaces/acme/boards/mt/tasks",
headers=HEADERS).json()["tasks"]
# Create a task
task = httpx.post(
f"{BASE}/workspaces/acme/boards/mt/tasks",
headers=HEADERS,
json={"title": "Deploy to production", "priority": "high"},
).json()["task"]
const TOKEN = process.env.HERMIIS_API_TOKEN;
const BASE = "https://hermiis.com/api/v1";
const headers = { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" };
// List boards
const boards = await fetch(`${BASE}/workspaces/acme/boards`, { headers })
.then(r => r.json()).then(b => b.boards);
// Create a task
const task = await fetch(`${BASE}/workspaces/acme/boards/mt/tasks`, {
method: "POST",
headers,
body: JSON.stringify({ title: "Deploy to production", priority: "high" }),
}).then(r => r.json()).then(b => b.task);
require 'net/http'
require 'json'
require 'uri'
TOKEN = ENV['HERMIIS_API_TOKEN']
BASE = 'https://hermiis.com/api/v1'
HDRS = { 'Authorization' => "Bearer #{TOKEN}", 'Content-Type' => 'application/json' }
# List tasks
uri = URI("#{BASE}/workspaces/acme/boards/mt/tasks")
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.get(uri.path, HDRS) }
tasks = JSON.parse(resp.body)['tasks']
# Create a task
uri = URI("#{BASE}/workspaces/acme/boards/mt/tasks")
resp = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |h|
req = Net::HTTP::Post.new(uri, HDRS)
req.body = { title: 'Deploy to production', priority: 'high' }.to_json
h.request(req)
end
task = JSON.parse(resp.body)['task']
<?php
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://hermiis.com/api/v1/',
'headers' => [
'Authorization' => 'Bearer ' . getenv('HERMIIS_API_TOKEN'),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
]);
// List tasks
$tasks = json_decode(
$client->get('workspaces/acme/boards/mt/tasks')->getBody(),
true
)['tasks'];
// Create a task
$task = json_decode(
$client->post('workspaces/acme/boards/mt/tasks', [
'json' => ['title' => 'Deploy to production', 'priority' => 'high'],
])->getBody(),
true
)['task'];
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
const base = "https://hermiis.com/api/v1"
func main() {
token := os.Getenv("HERMIIS_API_TOKEN")
client := &http.Client{}
// List tasks
req, _ := http.NewRequest("GET", base+"/workspaces/acme/boards/mt/tasks", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
// Create a task
payload, _ := json.Marshal(map[string]any{"title": "Deploy to production", "priority": "high"})
req, _ = http.NewRequest("POST", base+"/workspaces/acme/boards/mt/tasks", bytes.NewBuffer(payload))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, _ = client.Do(req)
defer resp.Body.Close()
}
use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, HeaderMap, HeaderValue};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let token = std::env::var("HERMIIS_API_TOKEN")?;
let base = "https://hermiis.com/api/v1";
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_str(&format!("Bearer {token}"))?);
// List tasks
let tasks: serde_json::Value = client
.get(format!("{base}/workspaces/acme/boards/mt/tasks"))
.headers(headers.clone())
.send().await?
.json().await?;
println!("{tasks:#?}");
// Create a task
let task: serde_json::Value = client
.post(format!("{base}/workspaces/acme/boards/mt/tasks"))
.headers(headers)
.header(CONTENT_TYPE, "application/json")
.json(&json!({"title": "Deploy to production", "priority": "high"}))
.send().await?
.json().await?;
println!("{task:#?}");
Ok(())
}
(ns hermiis-client.core
(:require [clj-http.client :as http]
[cheshire.core :as json]))
(def token (System/getenv "HERMIIS_API_TOKEN"))
(def base "https://hermiis.com/api/v1")
(def hdrs {:headers {"Authorization" (str "Bearer " token)
"Content-Type" "application/json"}
:as :json})
;; List tasks
(def tasks
(-> (http/get (str base "/workspaces/acme/boards/mt/tasks") hdrs)
:body :tasks))
;; Create a task
(def task
(-> (http/post (str base "/workspaces/acme/boards/mt/tasks")
(assoc hdrs :body (json/generate-string {:title "Deploy to production" :priority "high"})))
:body :task))