> ## Documentation Index
> Fetch the complete documentation index at: https://uselora.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Getting started

> Learn how to use Lora's API to manage resources in your Lora workspace programmatically.

## Endpoint

The API is REST over HTTPS. Plain HTTP isn't accepted.

```text theme={null}
https://api.uselora.com
```

## Authentication

Send your API key as a Bearer token on every request:

```bash theme={null}
Authorization: Bearer lora_sk_xxxx
```

## API key

### Create an API key

<Steps>
  <Step title="Go to your workspace">
    Open **Profile > API Keys** in your workspace.
  </Step>

  <Step title="Create the key">
    Click **Create** and pick the scopes this key should carry.
  </Step>

  <Step title="Use it">
    Send the key as a Bearer token on every request:

    ```bash theme={null}
    Authorization: Bearer lora_sk_xxxx
    ```
  </Step>
</Steps>

## Scopes

A key carries scopes that limit what it can do. Pick the narrowest set that lets the integration work. A leaked write-everything key is much worse than a leaked read-only one.

| Scope             | What it grants                                         |
| ----------------- | ------------------------------------------------------ |
| `shortcuts.read`  | Read shortcuts in the workspace.                       |
| `shortcuts.write` | Create, update, and delete shortcuts in the workspace. |
| `tags.read`       | Read tags in the workspace.                            |
| `tags.write`      | Create, update, and delete tags in the workspace.      |
| `folders.read`    | Read folders in the workspace.                         |
| `folders.write`   | Create, update, and delete folders in the workspace.   |

## Errors

Every error response is JSON with the same shape. `error` is human-readable. `requestId` correlates with the `X-Lora-Request-Id` response header; quote it when filing a support ticket. `traceId` shows up when the request hit a Sentry trace. `code` is optional, and when present it's a machine-readable handle you can branch on.

Minimum shape:

```json theme={null}
{
  "error": "Human-readable description of what went wrong.",
  "requestId": "fra1::iad1::abc123"
}
```

With a machine-readable code:

```json theme={null}
{
  "error": "One or more fields failed validation.",
  "code": "validation_error",
  "requestId": "fra1::iad1::abc123",
  "traceId": "0123456789abcdef0123456789abcdef"
}
```

### What a 401 means

The 401 body is identical whether the header is missing, malformed, the key is unknown, expired, or disabled. The API deliberately doesn't tell you which one. Distinguishing them on the wire would let an attacker probe key validity:

```json theme={null}
{ "error": "Unauthorized", "requestId": "fra1::iad1::abc123" }
```

There's no `code` field on the generic 401. Treat any 401 as "your request wasn't authenticated" and check the key you sent.

If the key is valid but lacks the scope an endpoint needs, you get `403` with `code: "insufficient_scope"` and a `required_scope` field naming what was missing.

### Error codes

Every machine-readable `code` the public `/v1/*` routes emit. Codes are stable. A value won't be repurposed. New codes may be added.

| Code                    | Status | When                                                                                                                                                                                                                                                                                                          |
| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validation_error`      | 400    | Schema validation failed. The body includes `fieldErrors` keyed by field name.                                                                                                                                                                                                                                |
| `invalid_json`          | 400    | The request body wasn't valid JSON, or wasn't a JSON object.                                                                                                                                                                                                                                                  |
| `reserved_slug`         | 400    | The submitted shortcut slug is on the reserved list (`POST /v1/shortcuts`).                                                                                                                                                                                                                                   |
| `no_default_folder`     | 400    | The workspace has no live folder for the tag's side-effect shortcut to land in (`POST /v1/tags`).                                                                                                                                                                                                             |
| `qr_data_too_long`      | 400    | The payload exceeds the capacity of the requested error correction level (`GET /v1/qr`).                                                                                                                                                                                                                      |
| `insufficient_scope`    | 403    | The API key is valid but lacks the scope an endpoint needs. Body includes `required_scope`, e.g. `"shortcuts.write"`.                                                                                                                                                                                         |
| `no_permission`         | 403    | The API key's user is not a member of the workspace the key is scoped to.                                                                                                                                                                                                                                     |
| `plan_requires_upgrade` | 403    | A requested feature isn't available on the workspace's plan. Body includes `current_plan` and `required_capability`. Today emitted by `GET /v1/qr` when an anonymous caller passes `hideLogo=true`. The `removeWatermark` capability unlocks on Basic and above; Free keys can't reach the public API at all. |
| `folder_not_found`      | 404    | The supplied `folderId` doesn't exist in this workspace (`POST /v1/shortcuts`).                                                                                                                                                                                                                               |
| `tag_not_found`         | 404    | One or more `tagIds` don't exist in this workspace (`POST /v1/shortcuts`, `PATCH /v1/shortcuts/{slug}`).                                                                                                                                                                                                      |
| `slug_conflict`         | 409    | A shortcut with this slug already exists in the workspace (`POST /v1/shortcuts`).                                                                                                                                                                                                                             |
| `tag_name_exists`       | 409    | A tag with this name already exists in the workspace (`POST /v1/tags`).                                                                                                                                                                                                                                       |
| `shortcut_slug_exists`  | 409    | The tag's auto-generated shortcut slug collides with an existing shortcut (`POST /v1/tags`).                                                                                                                                                                                                                  |
| `folder_name_exists`    | 409    | A folder with this name already exists in the workspace (`POST /v1/folders`).                                                                                                                                                                                                                                 |
| `rate_limit_exceeded`   | 429    | The API key's per-window request budget is exhausted. See the `Retry-After` header and [Rate limits](/essentials/rate-limits).                                                                                                                                                                                |
| `internal_error`        | 500    | Server-side failure. Safe to retry with backoff.                                                                                                                                                                                                                                                              |

### Idempotency

`GET` and `DELETE` are idempotent by definition. For `POST` and `PATCH`, send an `Idempotency-Key` header with a unique string so retries don't double-write:

```bash theme={null}
curl https://api.uselora.com/v1/shortcuts \
  -H "Authorization: Bearer $LORA_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "slug": "onboarding", "destination": "https://..." }'
```
