Custom API

Send signals to ProductBet from any source using the REST API.

Why use the Custom API?

The Custom API lets you send signals to ProductBet from any source that isn't covered by a built-in integration. Use it to connect internal tools, scripts, CRMs, or any system that can make HTTP requests.

Getting started

  1. Go to Settings > Integrations > Custom API and click Generate API Key.
  2. Copy your API key. It won't be shown again.
  3. Send a POST request to your ingest endpoint.

Endpoint

POST https://<your-deployment>.convex.site/ingest

Your endpoint URL is shown on the Custom API integration page after connecting.

Authentication

Include your API key in the Authorization header:

Authorization: Bearer pb_live_...

Request body

Send a JSON object (single signal) or an array of objects (batch, up to 100).

Required fields

FieldTypeDescription
idstringUnique identifier for this signal. Used for deduplication — sending the same id twice updates the existing signal instead of creating a new one.
contentstringThe text content to analyze. This is what the AI processes to extract insights.

Optional fields

FieldTypeDescription
urlstringLink back to the source (e.g. a ticket URL). Shown on the signal detail view.
tagsstring[]Tags to attach to the signal.
timestampnumberUnix timestamp in milliseconds. Defaults to the current time.
metadataobjectArbitrary key-value data stored alongside the signal.

Classification overrides

By default, ProductBet's AI classifies each signal's type, sentiment, actor, and topic. You can override any of these fields if your source system already knows the classification. When provided, these values are used directly and the AI result for that field is ignored.

FieldTypeAllowed values
typestringfeature_request, bug, support_ticket, deal_lost, deal_won, churn_signal
sentimentstringpositive, negative
actorstringcustomer, prospect, churned_user, team_member, partner
topicstringonboarding, core_workflow, reporting, integrations, search, notifications, admin, pricing, security, compliance, billing, support, competition, documentation, other

The AI still extracts the summary and goal fields regardless of overrides.

Examples

Minimal signal

curl -X POST https://<your-deployment>.convex.site/ingest \
  -H "Authorization: Bearer pb_live_..." \
  -H "Content-Type: application/json" \
  -d '{"id": "ticket-1234", "content": "Customer wants dark mode support"}'

Signal with classification overrides

curl -X POST https://<your-deployment>.convex.site/ingest \
  -H "Authorization: Bearer pb_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "id": "ticket-1234",
    "content": "Customer wants dark mode support",
    "type": "feature_request",
    "sentiment": "negative",
    "actor": "customer",
    "topic": "core_workflow",
    "url": "https://support.example.com/tickets/1234",
    "tags": ["ui", "theming"]
  }'

Batch request

curl -X POST https://<your-deployment>.convex.site/ingest \
  -H "Authorization: Bearer pb_live_..." \
  -H "Content-Type: application/json" \
  -d '[
    {"id": "fb-001", "content": "Login is too slow", "type": "bug"},
    {"id": "fb-002", "content": "Love the new dashboard", "sentiment": "positive"}
  ]'

Response

Success (200)

{"success": true, "scheduled": 1}

Validation error (400)

{"error": "body.type must be one of: feature_request, bug, support_ticket, deal_lost, deal_won, churn_signal"}

Authentication error (401)

{"error": "Invalid API key"}

Deduplication

The id field is your deduplication key. Sending a signal with the same id updates the existing record instead of creating a duplicate. Use stable, unique identifiers from your source system (e.g. ticket IDs, feedback IDs).

Troubleshooting

  • Signal not appearing? Make sure you're sending a unique id for each distinct signal. Reusing the same id updates the existing signal.
  • 400 error on classification fields? Check that the value matches one of the allowed values listed above. Values are case-sensitive.
  • 401 error? Verify your API key starts with pb_live_ and is included in the Authorization: Bearer header.