---
title: "Users · Scrape.ly Docs"
description: "Current user, profile, limits, plan and balance, usage, settings, and API key."
url: https://scrape.ly/docs/api-users
markdown: https://scrape.ly/docs/api-users.md
---

# Users

> Current user, profile, limits, plan and balance, usage, settings, and API key.

Account endpoints: the current user, profile, limits, plan and credit balance, usage, notification preferences, account settings, and the API key. All paths are relative to `https://api.scrape.ly`.

Every `/v2/users/me/*` route, `GET /v2/users/{userId}`, and the API key routes require your **user API key**. Run tokens, CI build tokens, and x402 payments are rejected with `403 insufficient-permissions`. The API key routes (`/v2/api-keys`, `/v2/scrapely`) also reject OAuth access tokens (for example from the hosted MCP server) with `403 insufficient-permissions`. Additional API keys can call these routes only as their [scopes](https://scrape.ly/docs/api-keys.md#scopes) allow: `billing:read` covers usage, limits, plan, and stats; `GET /v2/users/me` works with any key; the API key routes accept only the Default key.

Money fields ending in `Usd` are US dollars. Fields ending in `Nd` are nanodollars (`$1 = 1_000_000_000`). The same holds for run objects: `usageUsd` and `usageTotalUsd` are dollars, and `usageTotalNd` is nanodollars.

## Get current user

```
GET /v2/users/me
```

Returns the authenticated user: profile, plan, and account fields. The `plan` object keeps the shape existing API clients expect.

```bash [Request]
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/users/me
```

```json [Response]
{
  "data": {
    "id": "uSeRiDeXaMpLe123",
    "username": "janedoe",
    "email": "jane@example.com",
    "createdAt": "2026-01-15T10:30:00.000Z",
    "isPaying": true,
    "storeDiscount": 0,
    "profile": {
      "name": "Jane Doe",
      "bio": "Builds scrapers",
      "pictureUrl": "https://...",
      "githubUsername": "janedoe",
      "websiteUrl": "https://jane.example",
      "twitterUsername": "jane",
      "linkedinUrl": "https://linkedin.com/in/jane",
      "isPublic": true
    },
    "proxy": { "groups": ["RESIDENTIAL", "DATACENTER"] },
    "plan": {
      "id": "starter",
      "tier": "starter",
      "isEnabled": true,
      "monthlyUsageCreditsUsd": 49,
      "maxActorMemoryGbytes": 32,
      "maxActorCount": 20,
      "maxActorTaskCount": 50,
      "maxScheduleCount": 10,
      "maxConcurrentActorRuns": 8,
      "dataRetentionDays": 31,
      "supportLevel": "community",
      "enabledPlatformFeatures": ["ACTORS", "STORAGE", "PROXY", "SCHEDULER", "WEBHOOKS"],
      "planPricing": {
        "llmGatewayMarkup": 1.1,
        "chargeableServiceUnitPricesUsd": { "ACTOR_COMPUTE_UNITS": 0.3 }
      }
    },
    "effectivePlatformFeatures": { "SCHEDULER": { "isEnabled": true } }
  }
}
```

| Field | Notes |
| --- | --- |
| `isPaying` | `true` while your paid plan's renewal date is in the future. |
| `storeDiscount` | Percent discount on Store Actor events that allow discounts. |
| `plan.planPricing.chargeableServiceUnitPricesUsd` | Your per-unit platform prices (compute units, proxy, data transfer, storage operations). |
| `plan.planPricing.llmGatewayMarkup` | Your plan's [LLM gateway](https://scrape.ly/docs/llm-gateway.md#pricing) multiplier: each request costs OpenRouter's price times this. |
| `plan.enabledPlatformFeatures` | `SCHEDULER`, `WEBHOOKS`, and `ACTORS_PUBLIC_ALL` appear only on paid plans. |

The example is trimmed. The full `plan` object also includes `description`, `usageDiscountPercent`, and add-on price fields kept for client compatibility.

```api-test
{ "method": "GET", "path": "/v2/users/me" }
```

## Get a user by ID

```
GET /v2/users/{userId}
```

Pass your own ID or `me` to get the same object as `GET /v2/users/me`. For any other user ID, you get public fields only:

```json
{
  "data": {
    "id": "aNoThErUsErId456",
    "username": "acme",
    "name": "Acme Data",
    "imageUrl": "https://...",
    "createdAt": "2026-02-01T09:00:00.000Z"
  }
}
```

Unknown IDs return `404 user-not-found`.

## Profile

```
GET /v2/users/me/profile
PUT /v2/users/me/profile
```

Get or update your profile. `PUT` is partial: only fields you send change. Send `null` to clear a text field.

| Body field | Type | Notes |
| --- | --- | --- |
| `scrapelyUsername` | string | Public username, max 20 characters. Used in Actor paths (`username/actor-name`). See [Usernames](#usernames). |
| `bio` | string | Max 200 characters. |
| `readme` | string | Max 2000 characters. |
| `websiteUrl` / `linkedinUrl` | string | Must start with `http://` or `https://`. Max 500 characters. |
| `githubUsername` / `twitterUsername` / `discordUserId` | string | Optional social handles. Max 100 characters. |
| `isPublic` | boolean | Public profiles can be fetched without auth. Cannot be set to `false` while you have published Actors. |
| `showEmail` | boolean | Whether the public profile includes your email. |

```bash
curl -X PUT https://api.scrape.ly/v2/users/me/profile \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "bio": "Builds scrapers", "isPublic": true }'
```

These are the only fields `PUT` accepts. Any other field, such as `email`, `firstName`, or `lastName`, returns `400 invalid-input` with a message that names the field and lists the editable ones. To change your picture, use [Profile picture](#profile-picture).

`GET` returns the profile fields plus `imageUrl`, `createdAt`, Actor stats (`totalUsers`, `monthlyUsers`, `publicActors`, `totalRuns`, `successRate`, `avgRating`, `totalReviews`), and up to 100 of your public Actors. `PUT` returns the updated profile fields.

| Error | When |
| --- | --- |
| `400 invalid-input` | A field that isn't editable, a value of the wrong type, a username with characters that aren't allowed, an empty username, or a username starting with `x402_`. |
| `409` | A text field too long, invalid URL, username already taken, or unpublishing a profile that has published Actors. |
| `400 profanity-detected` | `scrapelyUsername`, `bio`, or `readme` contains blocked words. |

### Usernames

Your public username is the owner part of your Actor paths (`username/actor-name` and `username~actor-name`):

- It can have up to 20 ASCII letters, digits, `.`, `_`, and `-`, must start with a letter or digit, and can't contain `..`. Names starting with `x402_` are reserved.
- Usernames aren't case-sensitive. They are stored in lowercase, and a case variant of a taken name is taken too.
- You can change your username, but not remove it.
- Changing it moves all your Actors to the new name. Paths with your old username keep working and still lead to your Actors.
- A username stays with the account that first used it. Once you change it, nobody else can take the old one, and neither can anyone take the username of a deleted account. You can switch back to a username you used before.

### Profile picture

```
PUT /v2/users/me/profile/picture
DELETE /v2/users/me/profile/picture
```

Upload a picture as JSON or remove the current one. `imageData` is a base64 data URL. It is resized to 512×512 and stored as WebP.

```bash
curl -X PUT https://api.scrape.ly/v2/users/me/profile/picture \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "imageData": "data:image/png;base64,..." }'
```

`PUT` returns `{ "data": { "imageUrl": "data:image/webp;base64,..." } }`. `DELETE` returns `{ "data": { "success": true, "message": "Profile picture removed" } }`.

Errors: `400 missing-image` when `imageData` is absent, `400 image-too-large` when the image is over about 1 MB.

## Public profile

```
GET /v2/users/{username}/public-profile
```

Returns a user's public profile: name, avatar, bio, links, stats (`monthlyUsers` across their public Actors, `publicActors`, `successRate`, `avgRating`, `totalReviews`), and public Actors, each with `stats.totalUsers30Days`. `successRate` is the average of the public Actors' [success rates](https://scrape.ly/docs/api-actors.md), each over its current build or its last 30 days, leaving out Actors without enough runs in either; `null` when none has enough. No run or total-user counts are public. No authentication required. Private profiles return `404 user-not-found` unless you are the owner. `email` is included only when the user enabled `showEmail`.

```bash
curl https://api.scrape.ly/v2/users/janedoe/public-profile
```

```api-test
{ "method": "GET", "path": "/v2/users/janedoe/public-profile" }
```

## Stats

```
GET /v2/users/me/stats
```

Dashboard statistics for the last 30 days, with percentage deltas versus the previous 30 days. Cached for about 45 seconds.

```json
{
  "data": {
    "period": "last 30 days",
    "totalRuns": { "value": 42, "delta": 10, "period": "last 30 days" },
    "successRate": { "value": 95.2, "delta": 1.4, "failedRuns": 2 },
    "resultsScraped": { "value": 12000, "delta": 8 },
    "computeUsed": { "value": 3.14, "delta": -2 }
  }
}
```

```api-test
{ "method": "GET", "path": "/v2/users/me/stats" }
```

## Limits

```
GET /v2/users/me/limits
```

Your plan limits, with current usage where it applies.

```json
{
  "data": {
    "dataRetentionDays": 31,
    "userId": "uSeRiDeXaMpLe123",
    "limits": {
      "maxConcurrency": { "used": 1, "limit": 8 },
      "maxConcurrentBuilds": { "used": 0, "limit": 10 },
      "maxTotalMemoryGb": { "used": 2.0, "limit": 32 },
      "maxActorCount": { "used": 3, "limit": 20 },
      "maxActorTaskCount": { "used": 4, "limit": 50 },
      "maxScheduleCount": { "used": 1, "limit": 10 },
      "maxWebhookCount": { "used": 2, "limit": 10 },
      "minRunMemoryMbytes": 128,
      "maxRunMemoryMbytes": 32768,
      "maxBuildMemoryMbytes": 8192,
      "maxBuildTimeoutSecs": 1800,
      "maxApiRateLimit": 1000,
      "maxStorageRateLimit": 5000,
      "maxLlmRateLimit": 600,
      "maxLlmConcurrency": 20,
      "llmFreeDailyCapUsd": { "used": 0.12, "limit": 2.5 }
    },
    "freeTier": {
      "active": true,
      "maxConcurrency": 2,
      "maxRunMemoryMbytes": 4096,
      "maxScheduleCount": 5,
      "chatMessagesPerDay": { "used": 3, "limit": 30 },
      "monthlyFreeCreditUsd": 5
    }
  }
}
```

| Field | Meaning |
| --- | --- |
| `maxConcurrency` | Runs in progress at the same time. |
| `maxConcurrentBuilds` | Builds in progress at the same time. |
| `maxTotalMemoryGb` | Memory used by all running runs and builds combined. |
| `maxApiRateLimit` | API requests per minute for your account. |
| `maxStorageRateLimit` | Storage API requests per minute for your account. |
| `maxLlmRateLimit` | [LLM gateway](https://scrape.ly/docs/llm-gateway.md#limits) requests per minute for your account: your own limit, or the platform default. |
| `maxLlmConcurrency` | LLM gateway requests your account can have running at the same time: your own limit, or the platform default. |
| `freeTier` | The [free plan](https://scrape.ly/docs/account.md#free-plan) caps. While `active` is `true`, they are already applied to the limits above. `chatMessagesPerDay` is today's (UTC) AI chat usage, or `null` when there is no daily cap. `monthlyFreeCreditUsd` is the free credit added each month, or `null` on a paid plan. |
| `llmFreeDailyCapUsd` | [Free-plan daily LLM limit](https://scrape.ly/docs/llm-gateway.md#free-plan) in USD: `used` is today's (UTC) settled LLM charges, `limit` is the daily cap. Requests still in flight aren't in `used` yet. `null` on a paid plan, which has no daily limit. |

```api-test
{ "method": "GET", "path": "/v2/users/me/limits" }
```

## Plan

```
GET /v2/users/me/plan
```

Your current plan, this month's usage, and your credit balance.

```json
{
  "data": {
    "plan": {
      "id": "starter",
      "name": "Starter",
      "tier": "starter",
      "renewalDate": "2026-09-01T00:00:00.000Z"
    },
    "limits": {
      "maxConcurrency": 8,
      "maxConcurrentBuilds": 10,
      "maxTotalMemoryGb": 32
    },
    "usage": {
      "currentMonth": "2026-08",
      "usage": { "computeUnits": 41.2, "datasetWrites": 12000 },
      "usageUsd": {
        "computeUnits": 12.36,
        "datasetReads": 0.01,
        "datasetWrites": 0.06,
        "kvsReads": 0,
        "kvsWrites": 0,
        "rqReads": 0,
        "rqWrites": 0,
        "externalTransfer": 0,
        "internalTransfer": 0,
        "actorFee": 0,
        "browser": 0,
        "proxyResidential": 0,
        "proxyDatacenter": 0,
        "llm": 0.05
      },
      "usageTotalNd": 12430000000,
      "usageTotalUsd": 12.43
    },
    "billing": {
      "balance": 36.57,
      "usageTotalUsd": 12.43,
      "breakdown": {
        "subscription": 0,
        "subscription_paid": 36.57,
        "payg": 0,
        "dev_earnings": 0
      },
      "totalPaygPurchased": 0,
      "packageCapacity": 49,
      "packageConsumed": 12.43,
      "packageRemaining": 36.57
    }
  }
}
```

| Field | Meaning |
| --- | --- |
| `plan.renewalDate` | When the paid plan renews or expires. `null` on the free plan. |
| `usage.usageUsd.browser` / `proxyResidential` / `proxyDatacenter` / `llm` | This month's browser, residential proxy, datacenter proxy, and [LLM gateway](https://scrape.ly/docs/llm-gateway.md#pricing) charges. All are included in `usageTotalNd` and both `usageTotalUsd` fields. |
| `billing.balance` | Spendable credits in USD: `subscription` + `subscription_paid` + `payg` + `promo`. Expired credit is not included. |
| `billing.breakdown.promo` | Free credit: the free plan's monthly $5 and any promotional credit. Spent before any other credit. |
| `billing.promo` | Free credit details: `remaining`, `granted` and `consumed` (USD) of the unexpired free credit, and `expiresAt`, when the current free credit resets. |
| `billing.hasPaidPlan` | `false` while you're on the free plan and its limits apply. |
| `freeTier.active` | Same as `!billing.hasPaidPlan`. |
| `billing.breakdown.subscription` | Older free-plan credits. |
| `billing.breakdown.subscription_paid` | Credits included with your paid plan. |
| `billing.breakdown.payg` | Pay-as-you-go credits you bought. |
| `billing.breakdown.dev_earnings` | Earnings from your paid Actors. Not spent on runs. |
| `billing.packageCapacity` / `packageConsumed` / `packageRemaining` | Totals across your active credit packages. |
| `billing.credit` | The **Credit left** summary of the billing page, in USD: `periodStart`, `periodEnd`, `totalUsd`, `usedUsd`, `usedThisPeriodUsd`, `usedBeforePeriodUsd`, `paidFromEarlierCreditUsd`, `reservedUsd` (held for running work), `availableUsd`, `balanceUsd`, and `sources`. |
| `billing.credit.sources` | One entry per kind of unexpired credit you have: `plan` (plan credit), `additional` (additional credit), `free` (free monthly credit), or `prepaid` (prepaid credit), each with `amountUsd`, `remainingUsd`, and `expiresAt` (`null` for prepaid credit, which doesn't expire). |

```api-test
{ "method": "GET", "path": "/v2/users/me/plan" }
```

## Usage

```
GET /v2/users/me/usage/daily
GET /v2/users/me/usage/monthly
GET /v2/users/me/usage/actors
GET /v2/users/me/usage/pay-per-event
```

| Endpoint | Query | Description |
| --- | --- | --- |
| `/usage/daily` | `date=YYYY-MM-DD` | Usage quantities for one day. Defaults to today. |
| `/usage/monthly` | `date=YYYY-MM` | Service usage with prices, pay-per-event items, and a daily breakdown. Defaults to the current month. |
| `/usage/actors` | `date=YYYY-MM`, `limit`, `offset` | Per-Actor platform and developer charges for a month. |
| `/usage/pay-per-event` | `date=YYYY-MM`, `limit` (1-100, default 10), `offset`, `search`, `actorId`, `sort` (`amount`, `quantity`, `name`) | Paginated pay-per-event charges for a month. |

```bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  "https://api.scrape.ly/v2/users/me/usage/monthly?date=2026-08"
```

`/usage/monthly` returns:

- `usageCycle` with `startAt` and `endAt`.
- `monthlyServiceUsage`: one entry per service, each with `quantity`, `baseAmountUsd`, `baseUnitPriceUsd`, `amountAfterVolumeDiscountUsd`, and `priceTiers`. Services include `ACTOR_COMPUTE_UNITS`, `BROWSER_CREDITS`, `PROXY_RESIDENTIAL_TRANSFER_GBYTES`, `PROXY_DATACENTER_TRANSFER_GBYTES`, `LLM_TOKENS` ([LLM gateway](https://scrape.ly/docs/llm-gateway.md#pricing) input plus output tokens; prices differ by model, so `baseUnitPriceUsd` is the average price per token across the models used), `ACTOR_FEE`, dataset, key-value store, and request queue reads/writes, `DATA_TRANSFER_EXTERNAL_GBYTES`, `DATA_TRANSFER_INTERNAL_GBYTES`, and timed-storage GB-hours. `BROWSER_SECONDS` and `BROWSER_PROXY_TRANSFER_GBYTES` are informational only.
- `payPerEventItems` for pay-per-event Actor charges.
- `dailyServiceUsages`: `{ date, serviceUsage, totalUsageCreditsUsd }` per day.
- `totalUsageCreditsUsdBeforeVolumeDiscount`, `totalUsageCreditsUsdAfterVolumeDiscount`, and `usageTotalUsd`.

`/usage/daily` returns `{ "data": { "date": "2026-08-14", "usage": { ... } } }`, with counts such as `ACTOR_COMPUTE_UNITS`, `DATASET_WRITES`, and `DATA_TRANSFER_EXTERNAL_GB`.

`/usage/actors` returns `{ "data": { "month", "total", "count", "limit", "offset", "actors": [...] } }`.

`/usage/pay-per-event` returns `{ "data": { "month", "total", "count", "limit", "offset", "totalUsd", "items": [...], "actors": [...] } }`. Each item has the same shape as an entry in `payPerEventItems`. `total` counts the items that match the filters, and `totalUsd` is the whole month's pay-per-event total. `actors` lists `{ actorId, actorDisplayPath }` for every Actor with charges that month, whatever the filters.

```api-test
{ "method": "GET", "path": "/v2/users/me/usage/monthly" }
```

```api-test
{ "method": "GET", "path": "/v2/users/me/usage/daily" }
```

## Notification settings

```
GET /v2/users/me/notification-settings
PUT /v2/users/me/notification-settings
```

Get or update notification preferences. The response is a **flat object**, with no `data` wrapper and no nested `email` object. `PUT` is partial and returns the full settings object.

```bash [Request]
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/users/me/notification-settings
```

```json [Response]
{
  "lowBalanceEmail": true,
  "lowBalanceWeb": true,
  "lowBalanceWebhook": false,
  "lowBalanceWebhookUrl": null,
  "lowBalanceThresholdNd": 0,
  "storageExpirationEmail": true,
  "storageExpirationWeb": true,
  "scheduleFailureEmail": true,
  "scheduleFailureWeb": true,
  "consoleNotificationsEnabled": true,
  "emailNotificationsEnabled": true,
  "notificationEmail": "user@example.com",
  "notificationEmailVerified": true,
  "notificationEmailSource": "account",
  "customNotificationEmail": null,
  "accountEmail": "user@example.com",
  "pendingNotificationEmail": null,
  "issueCommentConsole": true,
  "issueCommentEmail": false,
  "messageConsole": true,
  "messageEmail": false,
  "actorAutoTestEmail": true,
  "actorAutoTestWeb": true,
  "actorMaintenanceEmail": true,
  "actorMaintenanceWeb": true,
  "actorPricingEmail": true,
  "actorPricingWeb": true,
  "planExpirationEmail": true,
  "planExpirationWeb": true,
  "creditExpirationEmail": true,
  "creditExpirationWeb": true,
  "apiKeyCreatedEmail": true,
  "apiKeyCreatedWeb": true
}
```

The example shows the defaults for a new account.

```bash
curl -X PUT https://api.scrape.ly/v2/users/me/notification-settings \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "lowBalanceThresholdNd": 10000000000, "lowBalanceEmail": true }'
```

Boolean preference fields: `lowBalanceEmail`, `lowBalanceWeb`, `lowBalanceWebhook`, `storageExpirationEmail`, `storageExpirationWeb`, `scheduleFailureEmail`, `scheduleFailureWeb`, `issueCommentConsole`, `issueCommentEmail`, `messageConsole`, `messageEmail`, `actorAutoTestEmail`, `actorAutoTestWeb`, `actorMaintenanceEmail`, `actorMaintenanceWeb`, `actorPricingEmail`, `actorPricingWeb`, `planExpirationEmail`, `planExpirationWeb`, `creditExpirationEmail`, `creditExpirationWeb`, `apiKeyCreatedEmail`, `apiKeyCreatedWeb`.

Delivery fields: `lowBalanceWebhookUrl`, `lowBalanceThresholdNd`, `consoleNotificationsEnabled`, `emailNotificationsEnabled`, `notificationEmail`.

`notificationEmail` is where emails go: a custom address you verified, otherwise your account email (`notificationEmailSource` is `custom` or `account`). `PUT` accepts it only as `null` or your account email (both switch back to the account email), or the current address. A new address returns `409 email_verification_required` and has to be verified with a code first; see [Notification email](https://scrape.ly/docs/notifications.md#notification-email).

Errors use a plain string body, for example `{ "error": "Invalid low balance webhook URL format" }`:

| Status | When |
| --- | --- |
| `400` | `lowBalanceWebhookUrl` is not a string or `null`, is not a valid URL, or points to a private or local address (`Webhook URL not allowed: ...`). |
| `500` | Settings could not be read or saved. |

See [Notifications](https://scrape.ly/docs/notifications.md) for which settings take effect and how delivery works.

```api-test
{ "method": "GET", "path": "/v2/users/me/notification-settings" }
```

## Permission approval skip

```
GET /v2/users/me/permission-approval-skip
PUT /v2/users/me/permission-approval-skip
```

When enabled, Actors that request full permissions run without a per-Actor approval. In the dashboard this is **Settings → Preferences → Skip permission approvals**.

`PUT` body: `{ "skip": true }`. A non-boolean `skip` returns `400 invalid-input`. Both methods return `{ "data": { "skipActorPermissionApprovals": true } }`.

```bash
curl -X PUT https://api.scrape.ly/v2/users/me/permission-approval-skip \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "skip": true }'
```

```api-test
{ "method": "GET", "path": "/v2/users/me/permission-approval-skip" }
```

## GitHub imports

```
GET /v2/users/me/transform-github-actors
PUT /v2/users/me/transform-github-actors
```

When you import an Actor from GitHub or a ZIP, Scrape.ly can adapt existing Actor source (SDK imports, base images) so the project runs on Scrape.ly. This setting turns that adaptation on or off. In the dashboard this is **Settings → Preferences → Transform actors**.

`PUT` body: `{ "transformGithubActors": true }`. A non-boolean value returns `400 invalid-input`. Both methods return `{ "data": { "transformGithubActors": true } }`.

```bash
curl -X PUT https://api.scrape.ly/v2/users/me/transform-github-actors \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "transformGithubActors": true }'
```

```api-test
{ "method": "GET", "path": "/v2/users/me/transform-github-actors" }
```

## API keys

Each account has a **Default key**, with full access to the account. It looks like `scrapely_` followed by 48 hex characters. You can also create additional keys with scopes, expiry, resource restrictions, IP allowlists, and spending caps. Creating, editing, regenerating, and revoking them is covered in [API keys & permissions](https://scrape.ly/docs/api-keys.md#manage-keys-over-the-api).

```
GET /v2/api-keys
POST /v2/api-keys/reset
```

`GET` lists every key with a masked prefix, never the full key. The Default key comes first, with the ID `default`. The full response shape is in [API keys & permissions](https://scrape.ly/docs/api-keys.md#manage-keys-over-the-api).

`POST /v2/api-keys/reset` rotates the Default key only. Your additional keys keep working.

`POST /v2/api-keys/reset` replaces the key immediately:

- The previous key stops working.
- All OAuth access tokens issued for your account (for example to MCP clients) are revoked.
- An "API key created" notification is sent (see [Notifications](https://scrape.ly/docs/notifications.md)).

The response is the only time the new key is returned through this endpoint:

```json
{
  "data": {
    "apiKey": "scrapely_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
}
```

```bash
curl -X POST -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/api-keys/reset
```

These endpoints are also limited to 10 requests per minute per IP address.

```api-test
{ "method": "GET", "path": "/v2/api-keys" }
```

## CLI helper

```
GET /v2/scrapely
```

Returns your user ID and full API key. Used by the CLI and the dashboard. There is no `data` wrapper.

```json
{
  "userId": "uSeRiDeXaMpLe123",
  "apiKey": "scrapely_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```

Returns `404` with `{ "error": "Scrapely user could not be found..." }` if the account has no key.

```bash
curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/scrapely
```

## Plan tiers

```
GET /v2/plan-tiers
```

Lists active plan tiers with their limits. Public, no authentication required. Responses are cacheable for 5 minutes.

```bash [Request]
curl https://api.scrape.ly/v2/plan-tiers
```

```json [Response]
{
  "data": [
    {
      "id": "starter",
      "display_name": "Starter",
      "display_order": 1,
      "max_concurrency": 8,
      "max_total_memory_gb": 32,
      "max_actor_count": 20,
      "max_task_count": 50,
      "max_schedule_count": 10,
      "max_webhook_count": 10,
      "min_run_memory_mb": 128,
      "max_run_memory_mb": 32768,
      "max_build_memory_mb": 8192,
      "max_build_timeout_minutes": 30,
      "max_api_rate_limit": 1000,
      "retention_days": 31,
      "support_level": "community",
      "has_account_manager": false,
      "store_discount": 0
    }
  ]
}
```

The values above are examples. Read the live tiers from this endpoint.

```api-test
{ "method": "GET", "path": "/v2/plan-tiers" }
```

---

- Previous: [Schedules](https://scrape.ly/docs/api-schedules.md)
- Next: [Store, reviews & issues](https://scrape.ly/docs/api-store.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
