npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lovable.dev/sdk

v7.1.3

Published

TypeScript SDK for the Lovable API

Readme

@lovable.dev/sdk

TypeScript SDK for the Lovable API.

Warning: experimental software. This SDK is not covered by the public API v1 stability guarantees. Any release, including minor and patch versions, may change or remove methods, types, and behavior. Pin an exact version. For production integrations, call the public API directly.

Installation

npm install @lovable.dev/sdk

Create an API key

Keys are created in the workspace settings at https://lovable.dev/settings/api-keys (the Access tokens tab). You need the admin or owner role in the workspace.

| Setting | Effect | | --- | --- | | Workspace | A key belongs to one workspace and can only read or change that workspace. Resources in other workspaces return 404. | | Plan | The workspace must be on Business or higher; every public v1 call from a lower plan returns 402 payment_required. Two operations (project PII labels, Security Center project inventory) require Enterprise. | | Access | Pick Read-only or Full access per resource. Projects maps to projects:read / projects:write, Workspace maps to workspaces:read / workspaces:write. A write scope includes the matching read scope. |

Every key from this page carries the public:v1 audience: it reaches the advertised public v1 management operations and nothing else. The audience is not shown in the settings page.

Quick start

import { LovableClient } from "@lovable.dev/sdk";

const client = new LovableClient({ apiKey: process.env.LOVABLE_API_KEY! });

const me = await client.me();
console.log(me.email, me.name);

// GET /v1/workspaces: list envelope (`data`, `pagination`) plus `workspaces`, an alias of `data`.
const { workspaces } = await client.listWorkspaces();
const workspaceId = workspaces[0].id;

// GET /v1/projects?workspace_id=...: same envelope, plus `projects` as an alias of `data`.
const page = await client.listProjects(workspaceId, { limit: 20 });
for (const project of page.data ?? []) {
  console.log(project.id, project.name, project.is_published ? "Published" : "Not published");
}

Every hand-written method returns the response body unchanged, apart from the aliases noted above. client.typed is an openapi-fetch client over the generated route types for any advertised operation without a hand-written method.

First-party clients targeting a baseUrl ending in /internal use client.internalTyped. It preserves the internal collaborator and member-sort names. Both accessors reject a base URL for the other surface; convenience methods support either base URL.

Pagination

List operations take limit (1 to 100, default 50) and cursor, and return data with pagination.next_cursor and pagination.has_more. Keep the same filters across pages.

let cursor: string | undefined;
do {
  const page = await client.listProjects(workspaceId, { limit: 100, cursor });
  for (const project of page.data ?? []) console.log(project.id);
  cursor = page.pagination.has_more ? (page.pagination.next_cursor ?? undefined) : undefined;
} while (cursor);

Create an embed URL

client.createEmbedUrl(projectId, parentOrigin) calls POST /v1/projects/{project_id}/embed-url with scope projects:write. It requires Business or higher and edit permission on the project. The response contains a preview URL valid for one hour and bound to one exact HTTPS parent origin. Visitors can view the built preview without a Lovable login.

Publish a project and poll the deployment

POST /v1/projects/{project_id}/publish (scope projects:write) returns 202 with a Deployment: id, status: "running", and null url, error_message, and error_class. The polling response uses the same schema; url is set only when that deployment completes. Poll GET /v1/projects/{project_id}/publish/{deployment_id} (scope projects:read) no more than once every 2 seconds. completed and error are terminal; keep polling on running and on unknown (a degraded, non-terminal state). Give up after a deadline; the example uses 10 minutes.

const { data: accepted } = await client.typed.POST("/v1/projects/{project_id}/publish", {
  params: { path: { project_id: projectId } },
  body: {}, // optional: { audience: "workspace" }
});
if (!accepted?.id) throw new Error("publish was not accepted");

const deadline = Date.now() + 10 * 60 * 1000;
let deployment;
do {
  if (Date.now() > deadline) throw new Error(`deployment ${accepted.id} did not finish in 10 minutes`);
  await new Promise((resolve) => setTimeout(resolve, 2000));
  ({ data: deployment } = await client.typed.GET("/v1/projects/{project_id}/publish/{deployment_id}", {
    params: { path: { project_id: projectId, deployment_id: accepted.id } },
  }));
} while (deployment?.status === "running" || deployment?.status === "unknown");

if (deployment?.status === "completed") console.log(deployment.url);
else console.error(deployment?.error_class, deployment?.error_message);

PATCH /v1/projects/{project_id}/publish updates audience and optional audience_targets without deploying, and returns the resolved audience and audience_targets. Read project GET for current is_published, publish_audience, and publish_audience_targets; the published URL is the url of the deployment that published it. An older version can remain published while a new deployment runs or fails. Use DELETE on the publish path to take the app offline.

Errors

Non-2xx responses throw ApiError. The API returns one error envelope on every operation:

{
  "type": "invalid_argument",
  "title": "Invalid argument",
  "status": 400,
  "request_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "detail": "validation failed",
  "errors": [{ "location": "query.limit", "message": "expected integer <= 100" }]
}

type, title, status, and request_id are always present. detail, errors[], and props are optional. Branch on type; title wording can change.

| Envelope field | On ApiError | | --- | --- | | status | status | | type | type | | title | message | | detail | detail | | props | props | | request_id, errors[] | Not exposed by the SDK. Both are in the response body when you call the API directly. |

import { ApiError } from "@lovable.dev/sdk";

try {
  await client.getProject(projectId);
} catch (err) {
  if (err instanceof ApiError && err.type === "project_not_found") return null;
  throw err;
}

Types you will meet first:

| status | type | Cause | | --- | --- | --- | | 401 | unauthorized | Missing, revoked, or expired key | | 402 | payment_required | Workspace plan below Business (or Enterprise where required) | | 403 | insufficient_scope | Key lacks the operation's scope | | 403 | forbidden | Caller lacks the workspace or project permission | | 403 | audience_forbidden | A public:v1 key called a route outside the public v1 contract | | 404 | project_not_found, workspace_not_found | Missing resource, or a resource in another workspace | | 406 | not_acceptable | Accept header excludes application/json | | 429 | rate_limited | Rate limit hit; see below |

Rate limits

Responses carry X-RateLimit-Limit, X-RateLimit-Remaining, and, when known, X-RateLimit-Reset (Unix seconds). Limits use a sliding window: per API key for key-authenticated requests, per user for session and OAuth requests. Publishing is limited separately (10 per minute by default), as are deployment status polling (120 per minute), analytics, security-center insights, and collaborator reads (300 per minute).

On 429 the client retries the request up to three times, waiting 100 ms, 300 ms, then 500 ms, or longer when the Retry-After header asks for more. After the last retry it throws ApiError with status: 429 and rateLimit.retryAfterMs parsed from Retry-After.

if (err instanceof ApiError && err.status === 429) {
  await new Promise((resolve) => setTimeout(resolve, err.rateLimit?.retryAfterMs ?? 1000));
}

Operations outside the public v1 contract

The client also exposes builder operations: project creation and remix (createProject, remixProject), messages (chat, listMessages, getMessage, waitForMessageCompletion, createVariant), database, git and file access, uploads, knowledge, skills, connectors, folders, and publish() (which calls the deprecated POST /v1/deployments alias). These routes are not part of the public v1 contract. A key created in the settings page receives 403 audience_forbidden from all of them. They require a key without a public audience, they carry no stability guarantee, and they are not documented here.

Workflows subpath

@lovable.dev/sdk/workflows defines compute-only durable workflows for the Cloudflare runtime; it does not call the Lovable API.

import { defineWorkflow, toWorker } from "@lovable.dev/sdk/workflows";

const workflow = defineWorkflow<{ message: string }, { message: string }>("normalize", async (ctx) => ({
  message: await ctx.step.run("normalize", () => ctx.input.message.trim()),
}));

export default toWorker(workflow);

Reference

| Document | Content | | --- | --- | | API_SPEC.md | Every advertised public v1 operation with its scope, parameters, and response schema | | https://api.lovable.dev/v1/openapi.json | The served OpenAPI document; authoritative when the two disagree | | examples/httpie.md | The same calls over plain HTTP | | src/types.ts | Exported request and response types |