Skip to content
API

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 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.

curl -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  https://api.scrape.ly/v2/users/me
FieldNotes
isPayingtrue while your paid plan's renewal date is in the future.
storeDiscountPercent discount on Store Actor events that allow discounts.
plan.planPricing.chargeableServiceUnitPricesUsdYour per-unit platform prices (compute units, proxy, data transfer, storage operations).
plan.planPricing.llmGatewayMarkupYour plan's LLM gateway multiplier: each request costs OpenRouter's price times this.
plan.enabledPlatformFeaturesSCHEDULER, 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.

GET/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 fieldTypeNotes
scrapelyUsernamestringPublic username, max 20 characters. Used in Actor paths (username/actor-name). See Usernames.
biostringMax 200 characters.
readmestringMax 2000 characters.
websiteUrl / linkedinUrlstringMust start with http:// or https://. Max 500 characters.
githubUsername / twitterUsername / discordUserIdstringOptional social handles. Max 100 characters.
isPublicbooleanPublic profiles can be fetched without auth. Cannot be set to false while you have published Actors.
showEmailbooleanWhether 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.

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.

ErrorWhen
400 invalid-inputA 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_.
409A text field too long, invalid URL, username already taken, or unpublishing a profile that has published Actors.
400 profanity-detectedscrapelyUsername, 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, 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
GET/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 }
  }
}
GET/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
    }
  }
}
FieldMeaning
maxConcurrencyRuns in progress at the same time.
maxConcurrentBuildsBuilds in progress at the same time.
maxTotalMemoryGbMemory used by all running runs and builds combined.
maxApiRateLimitAPI requests per minute for your account.
maxStorageRateLimitStorage API requests per minute for your account.
maxLlmRateLimitLLM gateway requests per minute for your account: your own limit, or the platform default.
maxLlmConcurrencyLLM gateway requests your account can have running at the same time: your own limit, or the platform default.
freeTierThe 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.
llmFreeDailyCapUsdFree-plan daily LLM limit 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.
GET/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
    }
  }
}
FieldMeaning
plan.renewalDateWhen the paid plan renews or expires. null on the free plan.
usage.usageUsd.browser / proxyResidential / proxyDatacenter / llmThis month's browser, residential proxy, datacenter proxy, and LLM gateway charges. All are included in usageTotalNd and both usageTotalUsd fields.
billing.balanceSpendable credits in USD: subscription + subscription_paid + payg + promo. Expired credit is not included.
billing.breakdown.promoFree credit: the free plan's monthly $5 and any promotional credit. Spent before any other credit.
billing.promoFree credit details: remaining, granted and consumed (USD) of the unexpired free credit, and expiresAt, when the current free credit resets.
billing.hasPaidPlanfalse while you're on the free plan and its limits apply.
freeTier.activeSame as !billing.hasPaidPlan.
billing.breakdown.subscriptionOlder free-plan credits.
billing.breakdown.subscription_paidCredits included with your paid plan.
billing.breakdown.paygPay-as-you-go credits you bought.
billing.breakdown.dev_earningsEarnings from your paid Actors. Not spent on runs.
billing.packageCapacity / packageConsumed / packageRemainingTotals across your active credit packages.
billing.creditThe 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.sourcesOne 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).
GET/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
EndpointQueryDescription
/usage/dailydate=YYYY-MM-DDUsage quantities for one day. Defaults to today.
/usage/monthlydate=YYYY-MMService usage with prices, pay-per-event items, and a daily breakdown. Defaults to the current month.
/usage/actorsdate=YYYY-MM, limit, offsetPer-Actor platform and developer charges for a month.
/usage/pay-per-eventdate=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 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.

GET/v2/users/me/usage/monthly
GET/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.

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

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.

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

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

See Notifications for which settings take effect and how delivery works.

GET/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 }'
GET/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 }'
GET/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.

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.

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

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.

GET/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.

curl https://api.scrape.ly/v2/plan-tiers

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

GET/v2/plan-tiers