HTTP POST Request Implementation
HTTP POST Request Implementation
Overview
Section titled “Overview”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)
Prerequisites
Section titled “Prerequisites”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
Key Concepts
Section titled “Key Concepts”HTTP POST Request Structure
Section titled “HTTP POST Request Structure”A TimeTagger API POST request to create a record requires:
POST /api/v2/records HTTP/1.1Host: localhost:8820Authorization: Bearer YOUR_TOKEN_HEREContent-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.
Record Object Fields
Section titled “Record Object Fields”| Field | Type | Required | Description |
|---|---|---|---|
key | String | Yes | Unique client-generated identifier |
t1 | Number | Yes | Start time (Unix timestamp in seconds) |
t2 | Number | Yes | End time (Unix timestamp in seconds) |
ds | String | Yes | Description of the time entry |
mt | Number | Yes | Modified time (Unix timestamp) |
st | Number | No | Server time (set automatically, can be 0) |
hidden | Boolean | No | Soft-delete flag (default: false) |
Node-RED HTTP Request Node Setup
Section titled “Node-RED HTTP Request Node Setup”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 │└─────────────────────────────────────────┘Implementation Steps
Section titled “Implementation Steps”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 variablesconst authToken = "eyJhbGciOiJIUzI1NiIs..."; // From TimeTagger Account
// Generate a unique key for this record// TimeTagger requires the client to generate unique keysfunction 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 nameconst description = msg.payload.description || "RFID Check-In";
// Create the record objectconst 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 recordsconst payload = [record];
// Set HTTP headersmsg.headers = { "Authorization": "Bearer " + authToken, "Content-Type": "application/json"};
// Set the request bodymsg.payload = payload;
return msg;Code Explanation:
| Element | Description |
|---|---|
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 header | Bearer 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 chronologicallyconst timestampHex = now.toString(16); // Convert to hex for compactnessconst 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;Step 3: Handle API Response
Section titled “Step 3: Handle API Response”After the POST request, process the response to confirm success:
// Function Node: "Handle POST Response"// Processes the TimeTagger API response
// Parse the responselet 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;Step 4: Test POST Endpoint in Node-RED
Section titled “Step 4: Test POST Endpoint in Node-RED”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": [] }]Step 5: Update Existing Records (PUT)
Section titled “Step 5: Update Existing Records (PUT)”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 fileconst existingRecord = msg.payload;
// Update end time and modified timeexistingRecord.t2 = now;existingRecord.mt = now;
msg.headers = { "Authorization": "Bearer " + authToken, "Content-Type": "application/json"};
// PUT method for updatesmsg.method = "PUT";msg.url = "http://localhost:8820/api/v2/records";msg.payload = [existingRecord]; // Still need array wrapper
return msg;Verification
Section titled “Verification”Verify the POST implementation:
# 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 requestcurl -s -X GET "http://localhost:8820/api/v2/records" \ -H "Authorization: Bearer YOUR_TOKEN" | json_pp
# Expected: Array of records including the newly created oneVerification 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
Troubleshooting
Section titled “Troubleshooting”Issue 1: “Invalid key” Error
Section titled “Issue 1: “Invalid key” Error”Symptom: API returns {"ok": false, "error": "Invalid key"}
Cause: Key contains invalid characters or is too short
Solution:
// Use only lowercase letters and digitsfunction createValidKey() { const now = Date.now().toString(36); const rand = Math.random().toString(36).substring(2, 12); return (now + rand).substring(0, 24);}Issue 2: “t1 >= t2” Error
Section titled “Issue 2: “t1 >= t2” Error”Symptom: API returns timestamp validation error
Cause: Start time is after or equal to end time
Solution:
// Ensure t1 < t2if (record.t1 >= record.t2) { node.warn("Fixing invalid timestamps: t1 >= t2"); record.t2 = record.t1 + 60; // Set minimum 1-minute duration}Issue 3: HTTP 413 Payload Too Large
Section titled “Issue 3: HTTP 413 Payload Too Large”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
Best Practices
Section titled “Best Practices”- ✅ Recommended: Use timestamp-based keys for chronological sorting
- ✅ Recommended: Validate
t1 < t2before 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
Summary
Section titled “Summary”- POST request requires a JSON array of record objects with
key,t1,t2,ds - Record keys are client-generated — use timestamp + random suffix for uniqueness
- Timestamps are Unix epoch in seconds (not milliseconds)
- PUT requests update existing records (used for check-out completion)
- API response confirms creation with
ok: trueand returns the server timestamp
References
Section titled “References”- TimeTagger API: Record Object Documentation
- Node-RED HTTP Request Node Guide
- Unix Timestamp Converter
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