HTTP POST Request Implementation
Esta página aún no está disponible en tu idioma.
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
We provide ESP32 ODM design-to-manufacturing services. From prototype to production — the team behind this tutorial can build it with you.
Talk to us →