---
title: "Messages & notifications · Scrape.ly Docs"
description: "Direct messages, conversations, and dashboard notifications."
url: https://scrape.ly/docs/api-messaging
markdown: https://scrape.ly/docs/api-messaging.md
---

# Messages & notifications

> Direct messages, conversations, and dashboard notifications.

Direct messages between Scrape.ly users, and the dashboard notification center. Paths are relative to `https://api.scrape.ly`.

All messaging routes require your **user API key**; run tokens are rejected with `403 insufficient-permissions`.

## Conversations

A conversation is a thread between two users. Every new conversation starts as `pending`. The recipient accepts or rejects it; only after acceptance can either side reply.

Rules for starting a conversation:

- Your profile must be **public** (`403 profile-not-public`).
- You can't message yourself (`403 forbidden`).
- If you already have a `pending` or `accepted` conversation with that user, you get `409 conversation-exists`. A rejected conversation doesn't block a new one.
- You can start at most **5** conversations per 24 hours (`429 rate-limited`).
- Message bodies are at most 10,000 characters and pass a profanity filter.

Sending (starting a conversation or replying) is also limited to **10 requests per minute** per user (`429 rate-limit-exceeded`).

### Start a conversation

```
POST /v2/users/{username}/messages
```

Sends the first message to a user, identified by their Scrape.ly **username**, and creates a pending conversation. The recipient gets a dashboard notification.

```bash
curl -X POST "https://api.scrape.ly/v2/users/janedoe/messages" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Hi! I have a question about your Actor" }'
```

`201` response:

```json
{
  "data": {
    "id": "conv-uuid",
    "status": "pending",
    "initialMessage": {
      "id": "msg-uuid",
      "body": "Hi! I have a question about your Actor",
      "senderId": "uSeRiDeXaMpLe123",
      "senderUsername": "acme",
      "createdAt": "2026-08-14T16:00:00.000Z"
    }
  }
}
```

### Find a conversation with a user

```
GET /v2/conversations/with/{username}
```

Returns your conversation with that user if one exists that isn't rejected and that you haven't deleted from your view. Otherwise returns `{ "data": null }`. Unknown usernames return `404 user-not-found`.

```json
{
  "data": {
    "id": "conv-uuid",
    "status": "accepted",
    "isInitiator": true,
    "otherUser": { "id": "uSeRiDeXaMpLe456", "username": "janedoe", "imageUrl": null },
    "createdAt": "2026-08-14T16:00:00.000Z",
    "updatedAt": "2026-08-14T16:05:00.000Z"
  }
}
```

```api-test
{ "method": "GET", "path": "/v2/conversations/with/janedoe" }
```

### List conversations

```
GET /v2/user/conversations
```

| Query param | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | string | — | `pending`, `accepted`, or `rejected`. |
| `limit` | number | 20 | Page size, max 100. |
| `offset` | number | 0 | Pagination offset. |

Conversations you deleted from your view are omitted.

```bash [Request]
curl "https://api.scrape.ly/v2/user/conversations?limit=20" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

```json [Response]
{
  "data": {
    "total": 1,
    "offset": 0,
    "limit": 20,
    "items": [
      {
        "id": "conv-uuid",
        "status": "accepted",
        "isInitiator": false,
        "otherUsername": "janedoe",
        "otherImageUrl": null,
        "lastMessage": {
          "body": "Thanks for the quick reply!",
          "senderId": "uSeRiDeXaMpLe456",
          "isOwn": false,
          "createdAt": "2026-08-14T16:05:00.000Z"
        },
        "unreadCount": 1,
        "createdAt": "2026-08-14T16:00:00.000Z",
        "updatedAt": "2026-08-14T16:05:00.000Z"
      }
    ]
  }
}
```

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

### Get a conversation

```
GET /v2/conversations/{conversationId}
```

Returns the same fields as [Find a conversation with a user](#find-a-conversation-with-a-user), plus `closedBy` (`{ "id", "username" }`, or `null` if the conversation is open). Conversations you aren't part of return `403 forbidden`; unknown conversations and ones you deleted from your view return `404 conversation-not-found`.

```bash
curl "https://api.scrape.ly/v2/conversations/{conversationId}" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

### List messages

```
GET /v2/conversations/{conversationId}/messages
```

Message history, oldest first. `limit` defaults to 50 (max 100), `offset` to 0. Fetching messages marks the conversation as read for you.

```bash [Request]
curl "https://api.scrape.ly/v2/conversations/{conversationId}/messages?limit=50&offset=0" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

```json [Response]
{
  "data": {
    "total": 2,
    "offset": 0,
    "limit": 50,
    "items": [
      {
        "id": "msg-uuid",
        "conversationId": "conv-uuid",
        "senderId": "uSeRiDeXaMpLe123",
        "senderUsername": "acme",
        "senderImageUrl": null,
        "body": "Hi! I have a question about your Actor",
        "isOwn": true,
        "createdAt": "2026-08-14T16:00:00.000Z"
      }
    ]
  }
}
```

### Send a message

```
POST /v2/conversations/{conversationId}/messages
```

Allowed only on **accepted** conversations that aren't closed (`403 forbidden` otherwise). Body: `{ "body": "..." }`.

```bash
curl -X POST "https://api.scrape.ly/v2/conversations/{conversationId}/messages" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "body": "Thanks for the quick reply!" }'
```

`201` response:

```json
{
  "data": {
    "id": "msg-uuid",
    "conversationId": "conv-uuid",
    "senderId": "uSeRiDeXaMpLe123",
    "senderUsername": "acme",
    "body": "Thanks for the quick reply!",
    "createdAt": "2026-08-14T16:05:00.000Z"
  }
}
```

The other participant gets a dashboard notification unless they turned off dashboard notifications for this conversation.

### Accept, reject, or close

```
PATCH /v2/conversations/{conversationId}/accept
PATCH /v2/conversations/{conversationId}/reject
PATCH /v2/conversations/{conversationId}/close
```

- Only the **recipient** can accept or reject, and only while the conversation is `pending`.
- Either participant can close an **accepted** conversation. After that, nobody can send messages.
- Acting on a conversation in the wrong state returns `400 invalid-action`.

```bash
curl -X PATCH "https://api.scrape.ly/v2/conversations/{conversationId}/accept" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

Accept returns `{ "data": { "id": "...", "status": "accepted" } }` and notifies the sender. Reject returns `status: "rejected"`. Close returns `{ "data": { "id": "...", "closedBy": "<userId>" } }` and notifies the other participant.

### Delete a conversation

```
DELETE /v2/conversations/{conversationId}
```

Hides the conversation from **your** list. The other participant still sees it. Returns `{ "data": { "success": true } }`.

```bash
curl -X DELETE "https://api.scrape.ly/v2/conversations/{conversationId}" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

### Conversation notification settings

```
GET /v2/conversations/{conversationId}/settings
PUT /v2/conversations/{conversationId}/settings
```

Your notification settings for one conversation. Defaults: `notifyConsole: true`, `notifyEmail: false`.

`PUT` replaces both values: a field you leave out is saved as `false`. Always send both.

```bash [Request]
curl -X PUT "https://api.scrape.ly/v2/conversations/{conversationId}/settings" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "notifyConsole": false, "notifyEmail": false }'
```

```json [Response]
{
  "data": {
    "notifyConsole": false,
    "notifyEmail": false
  }
}
```

> [!NOTE]
> `notifyConsole` controls dashboard notifications for replies. `notifyEmail` is stored, but no message emails are sent today.

## Dashboard notifications

The dashboard notification center (the bell icon). Notification `type` values include `low_balance`, `plan_expiration`, `credit_expiration`, `api_key_created`, `storage_digest`, `schedule_failure`, `actor_auto_test`, `actor_maintenance_expired`, `actor_pricing_changed`, `alert`, `issue_created`, `issue_comment`, `new_message`, `message_accepted`, `conversation_closed`, and `quota_reached`. See [Notifications](https://scrape.ly/docs/notifications.md) for when each is sent.

Errors on these routes use a plain string body, for example `{ "error": "Failed to fetch notifications" }`.

### List notifications

```
GET /v2/console-notifications
```

Newest first. `limit` defaults to 50 (max 100), `offset` to 0. The list is `data` (an array), with pagination fields at the top level.

```bash [Request]
curl "https://api.scrape.ly/v2/console-notifications?limit=20&offset=0" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

```json [Response]
{
  "data": [
    {
      "id": "cn_3f1c9a8e-1b2d-4c5e-9f00-123456789abc",
      "userId": "uSeRiDeXaMpLe123",
      "type": "new_message",
      "title": "New message from janedoe",
      "message": "janedoe: Thanks for the quick reply!",
      "link": "/messages/conv-uuid",
      "linkText": "View Message",
      "isRead": false,
      "createdAt": "2026-08-14T16:00:00.000Z"
    }
  ],
  "total": 3,
  "offset": 0,
  "limit": 20
}
```

Scrape.ly keeps roughly your newest 1,000 notifications and trims older ones.

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

### Unread count

```
GET /v2/console-notifications/unread-count
```

```json
{ "unreadCount": 3 }
```

There is no `data` wrapper.

```api-test
{ "method": "GET", "path": "/v2/console-notifications/unread-count" }
```

### Mark as read

```
PATCH /v2/console-notifications/read-all
PATCH /v2/console-notifications/{notificationId}/read
```

Both return `{ "success": true }`.

```bash
curl -X PATCH "https://api.scrape.ly/v2/console-notifications/read-all" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

### Delete all notifications

```
DELETE /v2/console-notifications
```

Deletes **all** of your dashboard notifications, read and unread. Returns `{ "success": true }`.

```bash
curl -X DELETE "https://api.scrape.ly/v2/console-notifications" \
  -H "Authorization: Bearer $SCRAPELY_TOKEN"
```

Account-wide email and dashboard notification preferences live in the [Users API](https://scrape.ly/docs/api-users.md#notification-settings).

## Errors

| Status | Type | Meaning |
| --- | --- | --- |
| 400 | `invalid-input` | Missing or empty message body, or body over 10,000 characters |
| 400 | `invalid-action` | Conversation is in the wrong state for accept, reject, or close |
| 400 | `profanity-detected` | Message failed the profanity filter |
| 403 | `forbidden` | Messaging yourself, not allowed to act on this conversation, or conversation not accepted or closed |
| 403 | `profile-not-public` | Your profile must be public to start a conversation |
| 404 | `user-not-found` / `conversation-not-found` | Unknown user or conversation |
| 409 | `conversation-exists` | A pending or accepted conversation already exists |
| 429 | `rate-limited` | More than 5 new conversations in 24 hours |
| 429 | `rate-limit-exceeded` | More than 10 sends per minute |

---

- Previous: [Earnings & monetization](https://scrape.ly/docs/api-earnings.md)
- Next: [Search, alerts & utilities](https://scrape.ly/docs/api-misc.md)
- All docs: [Docs index](https://scrape.ly/docs.md), [llms.txt](https://scrape.ly/llms.txt)
