Togi LogoTogi
Togi LogoTogi
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

HeaderDescription
X-API-KeyThe API key of the project
X-TimestampCurrent UNIX timestamp in milliseconds
X-SignatureHMAC-SHA256 of the timestamp

Signature Generation

To create a signature:

  1. Take the current timestamp in milliseconds as a string.
  2. Generate an HMAC-SHA256 signature using the project password as the key.
  3. 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

  1. Build the JSON object (e.g. for a decision):
{
  "title": "Decision Title",
  "description": "Details...",
  "options": ["Yes", "No"],
  "priority": "high"
}
  1. Serialize to string.
  2. Base64-decode the project password into 32 bytes.
  3. Generate a 16-byte random IV.
  4. Encrypt the plaintext using AES-256-CBC with the key and IV.
  5. Concatenate IV and ciphertext.
  6. Base64-encode the combined result.
  7. 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)