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)