Skip to content

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.

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 returns 400 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:

<return>?code=<one-time-code>&state=<state>

Decline (Deny) redirects to:

<return>?error=denied&state=<state>

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 incorrect client_secret.
  • 400 Missing code — no code in 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:

  • 400project_id, external_ref, and title are 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 matches project_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": [
    { "id": "<uuid>", "title": "Esatto", "status": "active" }
  ]
}

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": "uuid-of-task",
  "note": "Optional note for this entry"
}
  • 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:

{
  "status": "success",
  "entry_id": "uuid-of-time-entry",
  "start_time": "2026-06-10T07:22:00"
}

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 foundtask_id is 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 the completed status.

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 note to overwrite the entry note"
}
  • note (optional): If present, overwrites the note on the entry being stopped.

Response:

{
  "status": "success",
  "entry_id": "uuid-of-time-entry",
  "duration_seconds": 1830
}

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):

{
  "active": false
}

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:

{
  "status": "success",
  "entry_id": "uuid-of-time-entry",
  "duration_seconds": 5400
}

Errors:

  • 401 — missing, malformed, or unknown Bearer token, or a non-human account.
  • 403Not authorized to edit this entry: the caller is neither a platform admin nor an owner/admin of the entry's project squad.
  • 404Time entry not found: entry_id is missing, malformed, or does not match an existing entry.
  • 400end_time (after start_time) is required: end_time is 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) — true returns 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.
  • 403Not authorized to view this project's entries: the caller is neither a platform admin nor an owner/admin of the project's squad.
  • 404Project not found: project_id is 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