
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)