REST API
Togi REST API Specification
Version: v1
Base URL: https://togi-app.com/api/v1/
Format: JSON over HTTP
Authentication & Security
All endpoints that interact with project data require a valid HMAC-SHA256 signature using the project password.
Headers Required for Authenticated Requests
| Header | Description |
|---|---|
X-API-Key | The API key of the project |
X-Timestamp | Current UNIX timestamp in milliseconds |
X-Signature | HMAC-SHA256 of the timestamp |
Signature Generation
To create a signature:
- Take the current timestamp in milliseconds as a string.
- Generate an HMAC-SHA256 signature using the project password as the key.
- Base64 encode the result (no line breaks).
Example Code (Python):
import hmac, hashlib, base64
def generate_signature(password, timestamp):
sig = hmac.new(password.encode(), timestamp.encode(), hashlib.sha256).digest()
return base64.b64encode(sig).decode()Timestamp Validity
The X-Timestamp must not be older than 10 seconds compared to the server clock.
Decisions
Add Decision
POST /decision
Headers:
X-API-Key: <api_key>
X-Timestamp: <timestamp>
X-Signature: <signature>
Body:
{
"id": "<unique_id>",
"decision": {
// Allows linebreaks with '\n'
"title": "Decision Title",
"description": "Some details here\nAnd some details in the next line",
"options": ["Yes", "No", "An answer with\na linebreak"],
"priority": "high" // One of: "high", "medium", "low"
}
}Response: 202 Accepted
Delete Decision
DELETE /decision/{id}
Headers:
X-API-Key: <api_key>
X-Timestamp: <timestamp>
X-Signature: <signature>Response: 202 Accepted
Get All Decisions
GET /decisions Headers: X-API-Key: <api_key> X-Timestamp: <timestamp> X-Signature: <signature>
Response:
[
{
"id": "...",
"decision": { ... },
"created_at": 1710690000,
"answered": false
}
]Get Decision Count
GET /decisions/amount Headers: X-API-Key: <api_key> X-Timestamp: <timestamp> X-Signature: <signature>
Response:
{
"amount": 5
}Reports
Add Report
POST /report
Headers:
X-API-Key: <api_key>
X-Timestamp: <timestamp>
X-Signature: <signature>
Body:
{
"id": "<unique_id>",
"report": {
// Allows linebreaks with '\n'
"description": "This is a report"
}
}Response: 202 Accepted
Delete Report
DELETE /report/{id}
Headers:
X-API-Key: <api_key>Response: 202 Accepted
Get All Reports
GET /reports Headers: X-API-Key: <api_key> X-Timestamp: <timestamp> X-Signature: <signature>
Response:
[
{
"id": "...",
"report": { ... },
"created_at": 1710690000
}
]Answers
Get Answer
GET /answer/{id}
Headers:
X-API-Key: <api_key>
X-Timestamp: <timestamp>
X-Signature: <signature>Response:
{
"id": "...",
"answer": { ... },
// Indicates whether the decision was deleted instead of answered.
// In this case the answer contains an empty json.
"was_deleted": 0,
"created_at": 1710690000
}If not answered yet: 200 OK with []
Payload Encryption Format
Encrypted Fields
When encryption is enabled for a project, the following fields are encrypted:
decision(in/decision)answer(in/answer)report(in/report)
Encryption Steps
- Build the JSON object (e.g. for a decision):
{
"title": "Decision Title",
"description": "Details...",
"options": ["Yes", "No"],
"priority": "high"
}- Serialize to string.
- Base64-decode the project password into 32 bytes.
- Generate a 16-byte random IV.
- Encrypt the plaintext using AES-256-CBC with the key and IV.
- Concatenate IV and ciphertext.
- Base64-encode the combined result.
- Submit the encrypted value in the API request.
Example Code (C++)
std::string decision_str = decision_json.dump(); std::vector<unsigned char> iv(16); RAND_bytes(iv.data(), iv.size()); std::vector<unsigned char> key = base64Decode(project_secret); std::vector<unsigned char> encrypted = encryptAes256Cbc(decision_str, key, iv); std::vector<unsigned char> combined; combined.insert(combined.end(), iv.begin(), iv.end()); combined.insert(combined.end(), encrypted.begin(), encrypted.end()); std::string base64_payload = base64Encode(combined);
Example Code (Python)
import base64
import json
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad
def encrypt_payload(payload_dict, base64_project_secret):
# Convert project password from base64 to raw bytes (32 bytes)
key = base64.b64decode(base64_project_secret)
assert len(key) == 32, "Key must be 256 bits (32 bytes)"
# Convert JSON payload to string and then to bytes
plaintext = json.dumps(payload_dict).encode("utf-8")
# Generate random 16-byte IV
iv = get_random_bytes(16)
# Encrypt using AES-256-CBC
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
# Concatenate IV + ciphertext
combined = iv + ciphertext
# Return base64-encoded result
return base64.b64encode(combined).decode("utf-8")
# Example usage:
project_secret = "YWFhYmJiY2NjZGRkZWVlZmZmMDAwMDAwMDAwMDAwMDA=" # base64 of 32-byte key
decision_data = {
"title": "Do we continue?",
"description": "Make a choice",
"options": ["Yes", "No", "Maybe"],
"priority": "high"
}
encrypted = encrypt_payload(decision_data, project_secret)
print("Encrypted payload:", encrypted)