# How to Integrate Lovable with Everhour

- Tool: Lovable
- Difficulty: Intermediate
- Time required: 40 minutes
- Last updated: March 2026

## TL;DR

Integrating Everhour with Lovable requires Edge Functions to proxy the Everhour REST API. Store your Everhour API key in Cloud Secrets, create an Edge Function that reads and creates time entries, project budgets, and team timesheets, and build time tracking interfaces in Lovable's React frontend. Everhour is purpose-built for teams already using Asana, Jira, or Trello — this integration lets you embed time tracking directly into your own Lovable app while syncing with those connected PM tools.

## Why integrate Everhour with Lovable?

Everhour occupies a unique position in the time tracking market: it's designed from the ground up to work alongside popular project management tools — embedding natively into Asana, Jira, Trello, and Basecamp interfaces — while maintaining its own project budget and reporting layer. Teams use Everhour because they want time tracking that fits into their existing PM workflow rather than requiring a separate app context-switch. The API gives you access to all of Everhour's data: time entries with project and task associations, running timers, project budget vs actual comparisons, team member time reports, and client billing summaries.

Building a custom Lovable integration with Everhour enables scenarios that neither Everhour's native interface nor its PM integrations fully address: a unified operations dashboard that shows project budget health alongside other business metrics, a client-facing time report portal where clients can see hours logged against their project without accessing your Everhour account, a simplified mobile-first time logging interface for field teams who find Everhour's native mobile app too complex, and automated weekly timesheet review workflows where team leads get prompted to approve time entries before they're billed.

Everhour's REST API v1 uses API key authentication via the X-Api-Key request header — a straightforward scheme that makes Edge Function setup quick. Time entries are the core resource: each entry has a time (seconds), date, user ID, and an associated project/task. Projects have budgets with types (time-based in seconds, money-based in currency), and the budget vs actual comparison is available via the project's budget endpoint. This tutorial covers timer control, manual time logging, budget tracking, and team timesheet generation.

## Before you start

- A Lovable project with Cloud enabled
- An Everhour account with at least one project and API access (available on Team plan and above)
- An Everhour API key — generate at app.everhour.com → Team Settings → API → Generate new API key
- The Everhour project IDs you want to track against — found in project URLs or via the API
- Optional: Everhour connected to Asana, Jira, or Trello if you want to reference tasks from those tools in time entries

## Step-by-step guide

### 1. Generate your Everhour API key and store it in Cloud Secrets

Everhour API keys are workspace-level credentials — they give access to all data in your Everhour team account, including time entries from all team members. To generate one, log into Everhour and click the team settings icon (gear icon) in the top navigation. Select 'Team Settings' and navigate to the 'API' or 'Integrations' section. Click 'Generate API Key', copy it immediately, and give it a descriptive label like 'Lovable Integration'.

Note that API access in Everhour requires a Team plan or above — the free plan does not include API access. If you're on a free account and attempting to use the API, you'll receive 403 Forbidden errors on all requests. Confirm your plan before proceeding.

With the key copied, open your Lovable project, click the '+' button, and select the Cloud tab. Navigate to Secrets and click 'Add secret'. Enter EVERHOUR_API_KEY as the name and paste your key as the value. Save it. Unlike other APIs that use Bearer auth, Everhour sends its API key as an X-Api-Key header — your Edge Function will set this custom header on every request.

**Expected result:** EVERHOUR_API_KEY appears in Cloud Secrets with a masked value. The Everhour Team Settings → API page shows the key as active.

### 2. Create the Everhour proxy Edge Function

Everhour's REST API v1 uses the X-Api-Key custom header for authentication. Time entries in Everhour are identified by a composite key — they reference both a project ID and optionally a task ID using Everhour's internal task format (e.g., 'tm:12345' for Trello tasks, 'jira:PROJECT-123' for Jira issues, or plain numeric IDs for Everhour-native tasks). Understanding this task ID format is important if you're integrating with Everhour's PM tool connections.

The Edge Function below covers the most valuable operations: listing projects with budget data, fetching team time reports by date range, starting and stopping the timer for the authenticated user, creating manual time entries, and fetching per-project time summaries. The timer endpoints are user-scoped — the start/stop actions apply to the user whose API key is being used.

```
// supabase/functions/everhour-proxy/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";

const BASE = "https://api.everhour.com";
const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
};

serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }
  const apiKey = Deno.env.get("EVERHOUR_API_KEY");
  if (!apiKey) {
    return new Response(JSON.stringify({ error: "EVERHOUR_API_KEY not set" }), {
      status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
  const authHeaders = { "X-Api-Key": apiKey, "Content-Type": "application/json" };
  const { action, params = {} } = await req.json();
  let resp;

  switch (action) {
    case "list_projects": {
      const q = new URLSearchParams({ limit: String(params.limit || 100) });
      resp = await fetch(`${BASE}/projects?${q}`, { headers: authHeaders });
      break;
    }
    case "get_project":
      resp = await fetch(`${BASE}/projects/${params.projectId}`, { headers: authHeaders });
      break;
    case "list_time": {
      const q = new URLSearchParams();
      if (params.from) q.set("from", params.from);
      if (params.to) q.set("to", params.to);
      if (params.userId) q.set("user", params.userId);
      resp = await fetch(`${BASE}/team/time?${q}`, { headers: authHeaders });
      break;
    }
    case "get_project_time": {
      const q = new URLSearchParams();
      if (params.from) q.set("from", params.from);
      if (params.to) q.set("to", params.to);
      resp = await fetch(`${BASE}/projects/${params.projectId}/time?${q}`, { headers: authHeaders });
      break;
    }
    case "start_timer":
      resp = await fetch(`${BASE}/timers/current`, {
        method: "POST", headers: authHeaders,
        body: JSON.stringify({ task: { id: params.projectId }, ...(params.taskId && { task: { id: params.taskId } }) }),
      });
      break;
    case "stop_timer":
      resp = await fetch(`${BASE}/timers/current`, { method: "DELETE", headers: authHeaders });
      break;
    case "get_timer":
      resp = await fetch(`${BASE}/timers/current`, { headers: authHeaders });
      break;
    case "add_time":
      resp = await fetch(`${BASE}/time`, {
        method: "POST", headers: authHeaders,
        body: JSON.stringify({
          date: params.date, time: params.timeSeconds,
          task: { id: params.projectId },
          ...(params.comment && { comment: params.comment }),
        }),
      });
      break;
    case "list_members":
      resp = await fetch(`${BASE}/team/members`, { headers: authHeaders });
      break;
    default:
      return new Response(JSON.stringify({ error: `Unknown action: ${action}` }), {
        status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" },
      });
  }

  const data = await resp.json();
  return new Response(JSON.stringify(data), {
    status: resp.status, headers: { ...corsHeaders, "Content-Type": "application/json" },
  });
});
```

**Expected result:** The Edge Function deploys. Calling action 'list_projects' returns your Everhour projects with ID, name, and budget data. Calling 'list_members' returns your team members.

### 3. Build a project budget burn-down dashboard

Everhour's budget data is exposed on each project object — the budget field contains the type ('time' for hours-based, 'money' for currency-based) and the value (in seconds for time budgets). By comparing the budget value to total time logged, you can calculate burn percentage and project health. For trend analysis, fetching time entries by week over the past month shows logging velocity.

The dashboard component generates a project health overview that's useful for weekly agency reviews: color-coded burn indicators, time remaining calculations, and a pace indicator showing whether the current logging rate will exceed budget before the project end date.

**Expected result:** The budget dashboard renders project cards with accurate burn percentages calculated from Everhour data. Color coding correctly reflects project health status. The totals row shows aggregate statistics.

### 4. Implement timer controls and manual time logging

The most engaging part of a time tracking integration is real-time timer control — users should be able to start a timer, see it counting up, and stop it to log the session. The Everhour timer API provides start (POST /timers/current), stop (DELETE /timers/current), and status (GET /timers/current) endpoints. The GET endpoint returns the current running timer state including the start time, which your frontend can use to calculate elapsed time client-side without additional API calls.

For manual time entry (logging time after the fact), the Edge Function's add_time action creates a backdated entry on any past date. Combine this with a date picker and time input to create a manual log panel that team members can use to enter hours for work done without the timer running — a common scenario for meetings, calls, and focus sessions that happened before the user remembered to start the timer.

**Expected result:** The timer widget shows the current timer state and updates in real time. Start/stop controls work correctly. Manual time logging creates entries visible in Everhour immediately after submission.

## Best practices

- Always convert Everhour's second-based time values to hours before displaying to users — storing in seconds is correct for calculation but confusing to display raw.
- Cache project lists with a 10-minute TTL since projects change infrequently, but always refresh time entry data in real time for accurate burn rate calculations.
- Implement a maximum logging duration check before calling add_time — entries over 24 hours are almost always input errors and should prompt user confirmation.
- Use Everhour's date range filters (from/to) when fetching time reports for billing periods rather than loading all-time data and filtering client-side.
- For teams where different members track different projects, fetch only the projects relevant to the logged-in user rather than loading all team projects on every page load.
- Handle the case where a project has no budget gracefully in your UI — many projects are tracked in Everhour without formal budgets, and budget calculations should not error on null values.
- Test timer start/stop flows against Everhour's native interface in parallel to confirm entries are appearing correctly — API-logged time should be visible immediately in Everhour's standard reports.

## Use cases

### Project budget burn-down dashboard

An agency tracks all projects in Everhour with time-based budgets and wants a single-page dashboard showing budget health across all active projects. The Edge Function fetches projects with their budget settings and time totals, then calculates burn percentage and estimated time-to-budget-exceeded based on current logging velocity. The frontend renders a card grid with color-coded health indicators and a burn rate trend.

Prompt example:

```
Build a budget dashboard at /project-budgets. Fetch all active projects from my Everhour Edge Function. For each project, show: project name, budget hours, hours logged, hours remaining, burn percentage as a progress bar (green under 70%, yellow 70-90%, red over 90%), and estimated days until budget exhausted based on average daily hours this month. Sort projects by burn percentage descending. Add a filter to show all projects, only over-budget, or only under 50% burned. Show total hours across all projects in a summary card at the top.
```

### Team timesheet review and approval workflow

A project manager needs to review and approve weekly timesheets before client invoicing. The Edge Function fetches all time entries for the previous week grouped by team member, and the frontend presents an approval interface where the manager can review entries, add notes, and mark the week as approved — storing the approval in Supabase and triggering an invoice creation step.

Prompt example:

```
Build a timesheet review page at /timesheet-review. Add a week selector defaulting to last week. Fetch time entries for the selected week from my Everhour Edge Function grouped by user. Display each team member's entries in an expandable table: date, project name, task name, hours, and notes. Show weekly totals per person. Add an 'Approve' button per person that marks their timesheet as approved in a Supabase approvals table and sends them a notification. Show a summary: total team hours, billable vs non-billable split, and top projects by hours.
```

### One-click time logging from project task list

A development team uses a custom Lovable task tracker backed by Supabase and wants to add Everhour time logging directly to the task list — clicking a task starts the Everhour timer for that task's associated Everhour project, and clicking again stops it and logs the entry. The Edge Function starts and stops the Everhour timer API and returns the elapsed time to update the UI.

Prompt example:

```
Add time tracking buttons to the task list component. Each task row shows a play button. Clicking play calls my Everhour Edge Function to start a timer for the associated Everhour project ID (stored in the task's everhour_project_id column). Show a running timer clock on the active task row. Clicking stop calls the Edge Function to stop the timer and logs the time entry. Show a 'Logged today' badge on tasks with time entries today. Store the Everhour time entry ID in Supabase on the task record for reference.
```

## Troubleshooting

### All API requests return 403 Forbidden

Cause: Everhour API access requires a paid Team plan or above. Free accounts do not have API access and will receive 403 on all requests regardless of authentication.

Solution: Verify your Everhour plan at app.everhour.com → Team Settings → Plan & Billing. If you're on the free plan, you'll need to upgrade to Team to use the API. If you're on a paid plan and still getting 403, check that the API key was generated from the Team Settings API page (not a personal settings page) and that the key is still active.

### Time values in API responses are unexpectedly large numbers like 7200

Cause: Everhour stores all time values in seconds, not hours or minutes. A value of 7200 represents 2 hours (7200 / 3600 = 2.0).

Solution: Always convert seconds to hours for display using seconds / 3600. For displaying as hours and minutes, use Math.floor(seconds / 3600) for hours and Math.floor((seconds % 3600) / 60) for minutes. For budget comparisons, ensure both values are in seconds before comparing.

```
// Convert seconds to readable format
const toHoursDecimal = (seconds: number) => Math.round(seconds / 360) / 10;
const toHoursMinutes = (seconds: number) => {
  const h = Math.floor(seconds / 3600);
  const m = Math.floor((seconds % 3600) / 60);
  return `${h}h ${m}m`;
};
```

### Timer start returns 409 Conflict error

Cause: A timer is already running for the user. Everhour only allows one active timer per user at a time.

Solution: Before starting a new timer, call get_timer to check if one is already running. If yes, either stop the current timer first or prompt the user to stop it before starting a new one. Handle the 409 response in your UI by showing a message: 'Timer already running for [project name] — stop it before starting a new one.'

```
// Check before starting
const currentTimer = await callEdgeFunction('get_timer', {});
if (currentTimer.timer?.status === 'active') {
  // Show UI prompt to stop current timer first
  setConflictProject(currentTimer.task?.name);
  return;
}
```

### Time entries created via add_time don't appear in team reports

Cause: Time entries added via the API are created for the user associated with the API key. If the API key belongs to an admin and you're trying to log time for team members, the entries will appear under the admin's account, not the team member's.

Solution: To log time for specific team members, you need that member's API key or must use user-specific API tokens. Alternatively, use the user ID parameter in time entry creation to specify which team member the entry belongs to — check Everhour's API docs for the userId field on the POST /time endpoint.

## Frequently asked questions

### Does Everhour have a native connector in Lovable?

No, Everhour is not one of Lovable's 17 shared connectors. Integration requires an Edge Function that proxies the Everhour REST API v1. The API key is stored in Cloud Secrets and sent as an X-Api-Key header — it is never exposed to client-side code.

### Does Everhour's API provide access to time entries from connected Asana or Jira tasks?

Yes — when Everhour is connected to Asana or Jira, time entries are linked to those external task IDs. The API returns task IDs in the format 'as:ASANA_TASK_ID' or 'jira:ISSUE_KEY'. You can use these IDs to cross-reference Everhour time data with task data from those PM tools in the same dashboard.

### Can I use the Everhour API to generate invoices?

Everhour's API provides time entry and project data that you can use to calculate invoice amounts, but Everhour does not have invoice generation as a built-in API feature. For invoice generation, use the time data from Everhour combined with a billing integration like Stripe or QuickBooks in a separate Edge Function to create and send invoices based on tracked hours.

### What is the Everhour API rate limit?

Everhour does not publicly publish detailed rate limit numbers, but standard practice is to stay under 100 requests per minute per API key. For dashboard applications that load multiple project time summaries in parallel, add a small delay between requests or implement request batching to avoid hitting limits. Monitor Cloud → Logs for 429 responses as an indicator of rate limit pressure.

### How do I handle multiple team members each with their own Everhour accounts?

For a team dashboard where each member's data is aggregated, use an admin-level API key that has access to all team members' time entries — the /team/time endpoint returns entries across all users. For apps where individual users log their own time, you'll need each user to provide their API token, which you store per-user in Supabase's user settings table and pass dynamically to the Edge Function based on the authenticated user.

---

Source: https://www.rapidevelopers.com/lovable-integration/everhour
© RapidDev — https://www.rapidevelopers.com/lovable-integration/everhour
