Prime Bot
Home Docs Logs API

Logs API

Every send the campaign has made, with delivery state and Telegram-side message IDs.

The logs endpoint exposes a flat, time-ordered stream of outbound messages for a campaign. Inbound messages that triggered an auto-reply are referenced via triggered_by_message_id so you can stitch a conversation back together without a second call.

List logs

GET /api/v1/campaigns/{id}/logs — requires logs:read.

curl "https://prime-bot.live/api/v1/campaigns/1/logs?status=sent&per_page=100" \
  -H "Authorization: Bearer $TOKEN"

Query parameters

NameDefaultDescription
per_page25Page size, capped at 200.
page11-indexed page number.
status(none)sent, failed, or pending.
since(none)ISO 8601 timestamp. Only logs strictly after this are returned.
sender_id(none)Filter to a single contact's conversation.

Response shape

{
  "data": [
    {
      "id": 88212,
      "campaign_id": 1,
      "sender_id": 904,
      "direction": "outbound",
      "body": "Hey Jane, happy to send pricing over.",
      "status": "sent",
      "telegram_message_id": "1827361234",
      "triggered_by_message_id": "1827361231",
      "sequence_step": 0,
      "error": null,
      "sent_at": "2026-04-21T16:02:45Z",
      "created_at": "2026-04-21T16:02:42Z"
    }
  ],
  "meta": {
    "pagination": {
      "current_page": 1,
      "per_page": 100,
      "total": 1243,
      "last_page": 13
    }
  }
}

Understanding failures

When status is failed, the error field contains a short machine-readable code plus a human message:

{
  "status": "failed",
  "error": {
    "code": "USER_PRIVACY_RESTRICTED",
    "message": "The recipient has blocked messages from non-contacts."
  }
}

The most common codes are:

  • USER_PRIVACY_RESTRICTED — contact doesn't accept DMs from strangers. No retry.
  • PEER_FLOOD — your account is being rate-limited by Telegram. We back off automatically.
  • USER_DEACTIVATED — account has been deleted. No retry.
  • NETWORK — transient error. Retried up to 3 times with exponential backoff.

Polling efficiently

For near-real-time sync, keep track of the largest id you've seen and pass it as ?since_id= on the next call, or use the webhooks system to avoid polling entirely.

Edit this page on GitHub