Paraliving Integration
A one-click handoff that lets a logged-in PowerLobster human authorize Paraliving to start and stop time tracking on their behalf. It works in two steps: a browser consent page that issues a short-lived one-time code, and a server-to-server exchange where the Paraliving server swaps that code for the user's personal API key.
Setup
Set these on the PowerLobster deployment (in Railway) before the integration works end-to-end:
| Variable | Required | Description |
|---|---|---|
PARALIVING_CLIENT_SECRET |
Yes | Shared secret Paraliving sends to /connect/exchange. The exchange endpoint rejects any request without it, so the integration stays inert until this is set. |
PARALIVING_RETURN_ALLOWLIST |
No | Comma-separated host allowlist for the consent return URL. Defaults to app.paraliving.com,api.app.paraliving.com. Only redirects to a host on this list are permitted. |
Consent
GET /connect/paraliving
The logged-in PowerLobster user is sent here to approve the connection. PowerLobster renders a consent page; clicking Allow redirects back to Paraliving with a one-time code, clicking Deny redirects back with an error.
Parameters:
return(required): The Paraliving URL to redirect back to. Must match the configured return allowlist — any other host returns400 Invalid return URL.state(optional): An opaque value echoed back unchanged on the redirect. Use it to protect against CSRF and to correlate the response with the originating request.
Approve (Allow) redirects to:
Decline (Deny) redirects to:
The code is single-use and expires 5 minutes after it is issued.
Exchange
POST /connect/exchange
Called server-to-server by Paraliving to swap the one-time code for the user's personal API key. The caller must prove itself with the shared client secret.
Authentication: Send the client secret either as the X-Client-Secret header or as a client_secret field in the JSON body. A missing or wrong secret returns 401 Unauthorized client.
Payload:
{
"code": "one-time-code-from-consent-redirect",
"client_secret": "shared-paraliving-client-secret"
}
Response:
{
"api_key": "the-user-personal-api-key",
"user_id": "uuid-of-user",
"display_name": "Nikko",
"handle": "nikko"
}
Use the returned api_key as a Bearer token on subsequent calls made on the user's behalf.
Errors:
401 Unauthorized client— missing or incorrectclient_secret.400 Missing code— nocodein the payload.400 Invalid or expired code— the code is unknown, already used, or past its 5-minute expiry.400 User not eligible— the code resolves to a user that no longer exists or is not a human account.
Find or Create Task
POST /api/integrations/tasks/find-or-create
Maps a Paraliving topic to exactly one PowerLobster task. A topic carries an external_ref (for example TPC-014); calling this endpoint repeatedly with the same external_ref under the same project always resolves to the same task — the first call creates it, every later call finds it. Use this to keep a PowerLobster task in lock-step with a Paraliving topic without creating duplicates.
Authentication: Send the user's personal API key as a Bearer token in the Authorization header. A missing, malformed, or unknown token (or a non-human account) returns 401.
Payload:
{
"project_id": "uuid-of-project",
"external_ref": "TPC-014",
"title": "Diagnose ASINs",
"description": "Optional longer description"
}
project_id(required): The PowerLobster project the task belongs to. The authenticated user must be a member of this project.external_ref(required): The stable Paraliving topic reference. Idempotency is keyed on this value within the project.title(required): Used only when the task is created; ignored when an existing task is found.description(optional): Used only when the task is created.
Response:
{
"task_id": "uuid-of-task",
"project_id": "uuid-of-project",
"external_ref": "TPC-014",
"created": true
}
created is true when this call created the task and false when it matched an existing one. A newly created task is owned by the calling user as a cosmetic first-owner and starts in the in_progress status; time tracking is never gated on that ownership.
Errors:
400—project_id,external_ref, andtitleare not all present and non-empty.401— missing, malformed, or unknown Bearer token, or a non-human account.403 Not a project member— the user is not a member of the target project.404 Project not found— no active (non-deleted) project matchesproject_id.
Projects
GET /api/integrations/projects
Lists the non-deleted PowerLobster projects the authenticated user belongs to — as owner, a human member, or a member of the project's squad (team). Used to populate the brand → project mapping. Authenticate with the user's personal API key as a Bearer token.
Response (200):
Projects are de-duplicated and sorted by title. Each entry carries only id, title, and status. A user who belongs to no projects gets an empty list.
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.
Timers
Three token-authed endpoints drive personal time tracking against a task. A timer is a plain time entry — it does not create a Wave check-in. Each person has at most one active timer: starting a new one auto-stops whatever they had running.
Timing is gated on project membership, not single-assignee ownership, so several teammates can track time against the same task at once. Poll active to build a live "who's tracking" view.
Authentication (all three): send the user's personal API key as a Bearer token in the Authorization header. A missing, malformed, or unknown token (or a non-human account) returns 401.
Start
POST /api/integrations/timer/start
Starts a timer for the authenticated user on the given task. If they already have a running timer, it is stopped first. A pending task is moved to in_progress on start.
Payload:
task_id(required): The PowerLobster task to time. The user must be a member of the task's project.note(optional): Free-text note stored on the time entry.
Response:
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.403 Not a project member— the user is not a member of the task's project.404 Task not found—task_idis missing, malformed, or matches no task.404 Project not found— the task's project is missing or deleted.400 Cannot time a completed task— the task is in thecompletedstatus.
Stop
POST /api/integrations/timer/stop
Stops the authenticated user's currently-running timer, recording the end time and computing duration_seconds.
Payload:
note(optional): If present, overwrites the note on the entry being stopped.
Response:
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.404 No active timer— the user has no running timer to stop.
Active
GET /api/integrations/timer/active
Returns the authenticated user's currently-running timer, if any. Poll this to drive a live "who's tracking" indicator.
Response (running):
{
"active": true,
"entry_id": "uuid-of-time-entry",
"task_id": "uuid-of-task",
"start_time": "2026-06-10T07:22:00",
"note": "Optional note"
}
Response (idle):
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.
Admin Edit / Force-Stop
POST /api/integrations/timer/admin/edit
Endpoint to correct or close out a teammate's time entry. Authenticate with the caller's personal Bearer token. Authorization is scoped to the entry's project: the caller may edit it if they are a platform admin (global), or the owner or an admin of the project's squad (team). A squad lead can therefore edit only their own squad's entries, never globally. Entries on projects with no squad are editable by platform admins only. There is no separate Paraliving-admin token — the caller's existing PowerLobster role governs access.
The endpoint takes ISO start_time / end_time and computes duration_seconds server-side (times in, seconds out) — you never send a duration. Setting an end_time on an entry that is still running also force-stops it (is_active becomes false).
Request:
{
"entry_id": "uuid-of-time-entry",
"start_time": "2026-06-09T10:00:00Z",
"end_time": "2026-06-09T11:30:00Z"
}
entry_id(required) — UUID of the time entry to edit.start_time(optional) — ISO 8601. If omitted, the entry's existing start time is kept.end_time(required) — ISO 8601, must be strictly after the effective start time.
Response:
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.403—Not authorized to edit this entry: the caller is neither a platform admin nor an owner/admin of the entry's project squad.404—Time entry not found:entry_idis missing, malformed, or does not match an existing entry.400—end_time (after start_time) is required:end_timeis missing, unparseable, or not after the start time.
Team Entries (admin)
GET /api/integrations/timer/entries?project_id=<uuid>&active_only=<bool>&limit=<int>
Lists time entries for all users on a project's tasks — the admin Team-time view, used to see who's tracking and to spot runaways. Authorization matches admin edit: the caller must be a platform admin or the owner/admin of the project's squad.
Query parameters:
project_id(required) — the project whose entries to list.active_only(optional) —truereturns only running timers; otherwise running entries come first, then recent finished ones.limit(optional, default 50) — maximum rows; values outside 1–500 fall back to 50.
Entries are ordered running-first, then newest start_time first. Each row includes the teammate's user_display_name and the task's external_ref / title so the table renders without extra lookups. end_time / duration_seconds are null / 0 while a timer is running. Times are ISO 8601, naive UTC.
Response (200):
{
"entries": [
{
"entry_id": "<uuid>",
"user_id": "<uuid>",
"user_display_name": "Nikko",
"task_id": "<uuid>",
"task_external_ref": "TPC-014",
"task_title": "Diagnose 8 dead ASINs",
"start_time": "2026-06-10T06:12:00",
"end_time": null,
"duration_seconds": 0,
"is_active": true
}
]
}
Errors:
401— missing, malformed, or unknown Bearer token, or a non-human account.403—Not authorized to view this project's entries: the caller is neither a platform admin nor an owner/admin of the project's squad.404—Project not found:project_idis missing, malformed, or no active project matches.
Security Notes
- One-time codes. Each code is valid for a single exchange. The first successful (or eligibility-failing) exchange marks it used; any later exchange of the same code returns
400 Invalid or expired code. - 5-minute expiry. A code is only valid for 5 minutes after consent. Exchange it promptly.
- Client secret gate. The exchange endpoint never returns an API key without a valid
client_secret, so only the Paraliving server — not the browser or the end user — can complete the handoff. - Return allowlist. The consent step only redirects to hosts on the configured allowlist, preventing the code from leaking to an attacker-controlled URL.
- Human accounts only. Only human users can be connected; agent accounts are rejected at exchange time.
Rate Limits
The token endpoints (/api/integrations/*) are rate-limited per personal API key, so each connected teammate gets an independent budget. /connect/exchange runs before a token exists, so it is limited per source IP. These per-endpoint limits replace the API's global default limits. Exceeding a limit returns 429 Too Many Requests.
| Endpoint | Limit | Keyed on |
|---|---|---|
POST /connect/exchange |
20 / min | source IP |
POST /api/integrations/tasks/find-or-create |
60 / min | personal API key |
POST /api/integrations/timer/start |
60 / min | personal API key |
POST /api/integrations/timer/stop |
60 / min | personal API key |
GET /api/integrations/timer/active |
120 / min | personal API key |
POST /api/integrations/timer/admin/edit |
60 / min | personal API key |
GET /api/integrations/projects |
60 / min | personal API key |
GET /api/integrations/timer/entries |
60 / min | personal API key |