Skip to content

HTTP POST Request Implementation

HTTP POST Request Implementation

This section covers constructing and sending HTTP POST requests from Node-RED to the TimeTagger API to create time records. Learning this section will enable you to:

  • Construct a valid TimeTagger API POST request with JSON payload
  • Generate unique record keys for new time entries
  • Format timestamps correctly for the API
  • Handle API responses (success, failure, validation errors)

Before starting this section, ensure you have:

  • TimeTagger API token obtained (see 05-06)
  • Node-RED HTTP request node available
  • Understanding of JSON data format
  • Basic JavaScript knowledge for function nodes

A TimeTagger API POST request to create a record requires:

POST /api/v2/records HTTP/1.1
Host: localhost:8820
Authorization: Bearer YOUR_TOKEN_HERE
Content-Type: application/json
[JSON Body - Array of Record Objects]

Important: The request body must be a JSON array, not a single object, even when creating just one record.

FieldTypeRequiredDescription
keyStringYesUnique client-generated identifier
t1NumberYesStart time (Unix timestamp in seconds)
t2NumberYesEnd time (Unix timestamp in seconds)
dsStringYesDescription of the time entry
mtNumberYesModified time (Unix timestamp)
stNumberNoServer time (set automatically, can be 0)
hiddenBooleanNoSoft-delete flag (default: false)
Node-RED Node Configuration:
┌─────────────────────────────────────────┐
│ HTTP Request Node │
├─────────────────────────────────────────┤
│ Method: POST │
│ URL: http://localhost:8820/api/v2/records│
│ Authentication: None (use header) │
│ Enable SSL/TLS: No │
│ Return: UTF-8 String │
└─────────────────────────────────────────┘

Step 1: Create a Function Node for Record Payload

Section titled “Step 1: Create a Function Node for Record Payload”

The first step is to create a Node-RED function that generates a valid record object:

// Function Node: "Create Record Payload"
// Generates a new TimeTagger time entry
// Configuration - in production, use environment variables
const authToken = "eyJhbGciOiJIUzI1NiIs..."; // From TimeTagger Account
// Generate a unique key for this record
// TimeTagger requires the client to generate unique keys
function generateKey() {
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
let key = "";
for (let i = 0; i < 24; i++) {
key += chars.charAt(Math.floor(Math.random() * chars.length));
}
return key;
}
// Current timestamp (Unix seconds)
const now = Math.floor(Date.now() / 1000);
// Record description comes from the MQTT message payload
// msg.payload contains the RFID tag UID and user name
const description = msg.payload.description || "RFID Check-In";
// Create the record object
const record = {
key: generateKey(),
t1: now, // Start time = now
t2: now + 3600, // End time = now + 1 hour (placeholder)
ds: description,
mt: now,
st: 0 // Let server set this
};
// TimeTagger API expects an ARRAY of records
const payload = [record];
// Set HTTP headers
msg.headers = {
"Authorization": "Bearer " + authToken,
"Content-Type": "application/json"
};
// Set the request body
msg.payload = payload;
return msg;

Code Explanation:

ElementDescription
generateKey()Creates a 24-character random string as unique identifier
Math.floor(Date.now() / 1000)Current Unix timestamp in seconds
[record]Array wrapper (API requirement)
Authorization headerBearer token authentication

Step 2: Generate Properly Sorted Record Keys

Section titled “Step 2: Generate Properly Sorted Record Keys”

TimeTagger sorts records by their keys for ordering. Using timestamp-based keys ensures chronological ordering:

// Function Node: "Create Record with Timestamp Key"
// Uses timestamp-based key for chronological sorting
const authToken = global.get("timetaggerAuth").token;
const now = Math.floor(Date.now() / 1000);
// Key format: timestamp + random suffix
// This ensures records are sorted chronologically
const timestampHex = now.toString(16); // Convert to hex for compactness
const randomSuffix = Math.random().toString(36).substring(2, 10);
const recordKey = timestampHex + randomSuffix;
const record = {
key: recordKey,
t1: msg.payload.t1 || now,
t2: msg.payload.t2 || (now + 300), // Default 5-minute duration
ds: msg.payload.description || "Time entry via Node-RED",
mt: now,
st: 0
};
msg.headers = {
"Authorization": "Bearer " + authToken,
"Content-Type": "application/json"
};
msg.payload = [record];
return msg;

After the POST request, process the response to confirm success:

// Function Node: "Handle POST Response"
// Processes the TimeTagger API response
// Parse the response
let response;
try {
response = JSON.parse(msg.payload);
} catch (e) {
node.error("Failed to parse TimeTagger response: " + e.toString());
msg.success = false;
msg.error = "JSON parse error";
return msg;
}
if (response.ok === true) {
// Success! Record was created
msg.success = true;
msg.recordKey = response.key;
msg.statusCode = 201;
node.warn("TimeTagger record created: " + response.key);
} else {
// API returned an error
msg.success = false;
msg.error = response.error || "Unknown error";
msg.statusCode = response.http_status || 500;
node.warn("TimeTagger API error: " + msg.error);
}
return msg;

Create a test flow:

[Inject] → [Function: Create Record] → [HTTP Request] → [Function: Parse Response] → [Debug]

Test Flow Node-RED JSON (import this flow):

[
{
"id": "test-inject",
"type": "inject",
"name": "Test Create Record",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "{\"description\":\"Test from Node-RED\",\"t1\":0,\"t2\":0}",
"payloadType": "json",
"wires": [["test-create-record"]]
},
{
"id": "test-create-record",
"type": "function",
"name": "Create Record",
"func": "const authToken = \"YOUR_TOKEN_HERE\";\nconst now = Math.floor(Date.now() / 1000);\n\nconst key = now.toString(16) + Math.random().toString(36).substring(2, 10);\nconst record = {\n key: key,\n t1: msg.payload.t1 || now,\n t2: msg.payload.t2 || (now + 1800),\n ds: msg.payload.description || \"Test entry\",\n mt: now,\n st: 0\n};\n\nmsg.headers = {\n \"Authorization\": \"Bearer \" + authToken,\n \"Content-Type\": \"application/json\"\n};\n\nmsg.payload = [record];\nmsg.method = \"POST\";\nmsg.url = \"http://localhost:8820/api/v2/records\";\n\nreturn msg;",
"wires": [["test-http-request"]]
},
{
"id": "test-http-request",
"type": "http request",
"name": "POST to TimeTagger",
"method": "POST",
"ret": "txt",
"url": "http://localhost:8820/api/v2/records",
"tls": "",
"proxy": "",
"authType": "",
"x": 450,
"y": 280,
"wires": [["test-parse-response"]]
},
{
"id": "test-parse-response",
"type": "function",
"name": "Parse Response",
"func": "let response;\ntry {\n response = JSON.parse(msg.payload);\n} catch(e) {\n msg.success = false;\n msg.error = e.toString();\n return msg;\n}\n\nif (response.ok === true) {\n msg.success = true;\n msg.recordKey = msg.payload;\n node.warn(\"Record created successfully\");\n} else {\n msg.success = false;\n msg.error = response.error;\n}\n\nreturn msg;",
"wires": [["test-debug"]]
},
{
"id": "test-debug",
"type": "debug",
"name": "Result",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 650,
"y": 280,
"wires": []
}
]

For the check-out flow, we need to update an existing record with the end time:

// Function Node: "Update Record - PUT Request"
// Updates an existing record with the end time
const authToken = "YOUR_TOKEN_HERE";
const now = Math.floor(Date.now() / 1000);
// msg.payload contains the original record from the stored file
const existingRecord = msg.payload;
// Update end time and modified time
existingRecord.t2 = now;
existingRecord.mt = now;
msg.headers = {
"Authorization": "Bearer " + authToken,
"Content-Type": "application/json"
};
// PUT method for updates
msg.method = "PUT";
msg.url = "http://localhost:8820/api/v2/records";
msg.payload = [existingRecord]; // Still need array wrapper
return msg;

Verify the POST implementation:

Terminal window
# 1. Trigger the test flow in Node-RED
# 2. Check Debug tab for success response
# Expected successful response:
# {
# "ok": true,
# "key": "a1b2c3d4e5f6g7h8i9j0k1l2",
# "st": 1715942400,
# "from_epoch": 0,
# "to_epoch": 0
# }
# 3. Verify in TimeTagger Web UI
# Open http://localhost:8820
# The new entry should appear in the timeline
# 4. Verify via API GET request
curl -s -X GET "http://localhost:8820/api/v2/records" \
-H "Authorization: Bearer YOUR_TOKEN" | json_pp
# Expected: Array of records including the newly created one

Verification Checklist:

  • POST request returns HTTP 201 (Created)
  • New record appears in TimeTagger web UI
  • Description matches what was sent
  • Timestamps are correct (t1 < t2)
  • Key is unique (no duplicate key errors)
  • PUT request updates existing record correctly

Symptom: API returns {"ok": false, "error": "Invalid key"}

Cause: Key contains invalid characters or is too short

Solution:

// Use only lowercase letters and digits
function createValidKey() {
const now = Date.now().toString(36);
const rand = Math.random().toString(36).substring(2, 12);
return (now + rand).substring(0, 24);
}

Symptom: API returns timestamp validation error

Cause: Start time is after or equal to end time

Solution:

// Ensure t1 < t2
if (record.t1 >= record.t2) {
node.warn("Fixing invalid timestamps: t1 >= t2");
record.t2 = record.t1 + 60; // Set minimum 1-minute duration
}

Symptom: HTTP status 413 when sending large arrays

Cause: Sending too many records in a single request

Solution: Batch records in groups of 50-100

  • Recommended: Use timestamp-based keys for chronological sorting
  • Recommended: Validate t1 < t2 before sending the request
  • Recommended: Handle HTTP 409 (Conflict) for duplicate key errors by retrying with a new key
  • Avoid: Sending more than 100 records per API request
  • Avoid: Hardcoding tokens directly in function node code
  1. POST request requires a JSON array of record objects with key, t1, t2, ds
  2. Record keys are client-generated — use timestamp + random suffix for uniqueness
  3. Timestamps are Unix epoch in seconds (not milliseconds)
  4. PUT requests update existing records (used for check-out completion)
  5. API response confirms creation with ok: true and returns the server timestamp

Writing Date: 2026-05-17
Based on Source File: 校正版/10 Time recording witht RFID und TimeTagger.md
Target Audience: Alibaba.com IoT Pre-sales Engineer
Status: ✅ Completed