Peeved
Features Templates Get Peeved

Integrations API

Build on top of any Topic with a per-Topic API key.

Every Topic in Peeved can issue its own API key. The key is scoped to a single Topic and a chosen subset of permissions (read, create, edit, delete). Use it to read entries, write new ones, patch structured fields, send chat messages, or subscribe to live changes — all without going through the iOS app.

Contents

  1. Create an API key
  2. Authentication
  3. Scopes
  4. Rate limits
  5. Endpoints
  6. Datapoints — schema-aware writes
  7. WebSocket — live updates
  8. Errors
  9. Worked example
  10. Notes

1. Create an API key

  1. Open the Peeved iOS app and select the Topic you want to integrate with.
  2. Tap the Edit Topic icon in the header, then scroll to Integrations.
  3. Tap New integration. Give it a name (e.g. "My todo bot") and choose its scopes.
  4. Peeved generates a key prefixed with pvi_. Copy it now — it's shown once and never again. If you lose it, delete the integration and create a new one.

Each integration is bound to one Topic. To act on multiple Topics, create multiple integrations. There's no shared / account-wide key by design — blast radius is always one Topic.

2. Authentication

Send the key as the X-API-Key request header. The base URL is:

https://interfacec-server.onrender.com

Quick sanity check:

curl -H "X-API-Key: pvi_your_key_here" \
  https://interfacec-server.onrender.com/api/integrations/v1/whoami
{
  "success": true,
  "data": {
    "integration_id": 1,
    "thread_id": 334,
    "name": "My todo bot",
    "scopes": ["read", "create", "edit", "delete"]
  }
}

3. Scopes

ScopeEndpoints it unlocks
readList / get thread items, get the Topic schema, whoami
createPOST a new thread item
editPATCH an existing thread item
deleteDELETE a thread item

Pick the minimum set you need. You can rotate by deleting and re-creating the integration.

4. Rate limits

Limits are per-integration, sliding-window, in requests per minute. Exceeding a limit returns HTTP 429 with a JSON error body.

OperationLimit
Read (list / get / whoami)30 / minute
Create3 / minute
Edit10 / minute
Delete10 / minute
Chat6 / minute

5. Endpoints

GET /api/integrations/v1/whoami

Returns the integration's id, thread id, name, and scopes. Useful as a connectivity check.

GET /api/integrations/v1/thread

Returns the Topic this key is bound to — including its schema.columns, which is what you'll use to send valid datapoints. Example:

{
  "success": true,
  "data": {
    "id": 334,
    "name": "Software todo",
    "description": "Task tracking and status",
    "schema": {
      "columns": [
        { "key": "taskName", "type": "string", "label": "Task Name" },
        { "key": "priority", "type": "picklist", "label": "Priority",
          "picklistValues": [
            { "value": "High",   "color": "#FF3B30" },
            { "value": "Medium", "color": "#FF9F0A" },
            { "value": "Low",    "color": "#34C759" }
          ]
        },
        { "key": "status", "type": "picklist", "label": "Status",
          "picklistValues": [
            { "value": "Backlog",      "color": "#8E8E93" },
            { "value": "In Progress",  "color": "#30B0FF" },
            { "value": "Review",       "color": "#AF52DE" },
            { "value": "Completed",    "color": "#34C759" },
            { "value": "Ready for Dev","color": "#E5B800" }
          ]
        },
        { "key": "estimate", "type": "double",        "label": "Estimate", "units": "hrs" },
        { "key": "tags",     "type": "array[string]", "label": "Tags" }
      ]
    }
  }
}

GET /api/integrations/v1/thread-items

Lists items, newest first. Optional query: ?limit=N (default 50, max 200).

GET /api/integrations/v1/thread-items/:itemId

Returns a single item. Response shape (same as list rows):

{
  "success": true,
  "data": {
    "id": 3685,
    "thread_id": 334,
    "parent_id": null,
    "type": "user",
    "content": "Remove the trashcan fade-in animation from the main header",
    "image_url": null,
    "thumb_url": null,
    "mood": 5,
    "datapoints": {
      "taskName": "Remove trashcan fade-in animation",
      "priority": "Low",
      "status":   "In Progress",
      "estimate": 1,
      "tags":     ["UI", "Header", "Frontend"]
    },
    "integration_id":  1,
    "integration_name": "My todo bot",
    "created_at":  "2026-05-03T14:47:27.448Z",
    "modified_at": "2026-05-03T15:12:08.001Z"
  }
}

datapoints is always present and is always an object (possibly empty). Keys match the Topic's schema.columns[].key.

POST /api/integrations/v1/thread-items

Create a new item. Optional body fields: content, image_url, thumb_url, mood, type, datapoints.

curl -X POST https://interfacec-server.onrender.com/api/integrations/v1/thread-items \
  -H "X-API-Key: pvi_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Wire up onboarding analytics",
    "datapoints": {
      "taskName": "Wire up onboarding analytics",
      "priority": "High",
      "status":   "Backlog",
      "estimate": 3,
      "tags":     ["Analytics", "Onboarding"]
    }
  }'

PATCH /api/integrations/v1/thread-items/:itemId

Update one or more fields. Send only the keys you want to change — datapoints is merged with the existing values.

curl -X PATCH https://interfacec-server.onrender.com/api/integrations/v1/thread-items/3685 \
  -H "X-API-Key: pvi_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "datapoints": { "status": "In Progress" } }'

DELETE /api/integrations/v1/thread-items/:itemId

Permanently removes the item. Returns { "success": true, "data": { "id": 3685 } }.

POST /api/integrations/v1/chat

Post a chat message to the Topic's chat thread. The author is the integration's name (so users can tell the messages came from your bot, not them). Body: { "content": "..." }.

6. Datapoints — schema-aware writes

datapoints is the same on every Topic, regardless of schema — a flat JSON object keyed by schema.columns[].key. The server validates each value against its column type:

Column typeAccepted JSON
string / text / dateString
double / integerNumber (or numeric string — coerced)
booleantrue / false
picklistString matching one of picklistValues[].value
array[string]Array of strings

Unknown column keys are rejected with 400. Picklist values that aren't in the allowed list are rejected with 400 and a list of allowed values in the error message.

On PATCH, datapoints is merged with the existing record — so to flip one field (e.g. status), you don't need to resend the others.

7. WebSocket — live updates

Every Peeved account has a WebSocket service that fans out item-level events as they happen. Connect with the same API key you use for HTTP calls and you'll receive every event for the user who owns the integration's Topic — filter to your thread_id client-side to ignore the rest.

The transport is socket.io (v4). Endpoint:

wss://interfacec-server-ws.onrender.com

Connecting

// Node.js — npm i socket.io-client
const { io } = require("socket.io-client");

const API_KEY   = process.env.PEEVED_API_KEY;        // pvi_...
const THREAD_ID = 334;                                // your integration's thread

const socket = io("https://interfacec-server-ws.onrender.com", {
  transports: ["websocket"],
  // socket.io-client only sends headers on the (optional) HTTP polling
  // handshake. The Peeved WS server therefore also accepts the key as a
  // query param so a websocket-only connection can authenticate.
  extraHeaders: { "X-API-Key": API_KEY },
  query:        { apiKey:      API_KEY },
});

socket.on("connect",      () => console.log("connected", socket.id));
socket.on("connect_error", (err) => console.error("auth failed:", err.message));

socket.on("message", (raw) => {
  const msg = typeof raw === "string" ? JSON.parse(raw) : raw;
  if (msg.threadId !== THREAD_ID) return;            // not for us
  console.log(msg.type, msg.id, msg.data);
});

What you'll receive

The server emits a single event name — "message" — and the payload is JSON with a type discriminator. The types most useful to integrations:

TypeWhenPayload (key fields)
integration_thread_item_newAn integration created an item via POST.threadId, id, data (full row, including ai_response)
threadItemQueue_*Worker queued / completed processing for an item (analysis, schema generation, chart recs).threadId, id, status, data
threadQueue_*Thread-level work (e.g. chart/metric recommendations) finished.threadId, data

The full DB row inside data uses the legacy iOS shape (e.g. ai_response is a JSON string). If you want the clean integration-API shape with parsed datapoints, treat WS events as a notification and call GET /api/integrations/v1/thread-items/:itemId to read the canonical record.

Tips

  • Filter by threadId client-side. The server fans out the entire user feed (including non-integration activity) to every authenticated socket on that user's room. Drop anything whose threadId isn't yours.
  • Reconnect on drop. socket.io reconnects automatically; on reconnect, run a one-shot GET /thread-items?limit=N to backfill anything you missed.
  • De-duplicate by id. WS pushes can arrive out of order with HTTP responses; using the highest-seen id per thread is the simplest reconciliation.
  • Heartbeats are automatic. Don't send your own ping — socket.io handles keep-alive.
  • Auth is one-shot at handshake. If you delete the integration, existing sockets keep working until they disconnect; the next reconnect fails with "Authentication error".

8. Errors

Every error response is JSON with success: false and a human-readable message:

{ "success": false, "message": "\"status\" must be one of: Backlog, In Progress, Review, Completed, Ready for Dev" }
StatusMeaning
400Validation failed (bad column key, wrong type, picklist mismatch, no updatable fields).
401Missing or invalid X-API-Key.
403Key is valid but missing the required scope for the operation.
404Item or thread not found, or doesn't belong to this integration.
429Rate limit exceeded. Back off and retry.
5xxServer error. Safe to retry with backoff.

9. Worked example — flip a todo to "In Progress"

# 1. List recent items
curl -H "X-API-Key: pvi_..." \
  https://interfacec-server.onrender.com/api/integrations/v1/thread-items?limit=5

# 2. Pick one (id: 3685) and flip its status
curl -X PATCH https://interfacec-server.onrender.com/api/integrations/v1/thread-items/3685 \
  -H "X-API-Key: pvi_..." \
  -H "Content-Type: application/json" \
  -d '{ "datapoints": { "status": "In Progress" } }'

# 3. Confirm the merge — other datapoints (priority, tags, ...) untouched
curl -H "X-API-Key: pvi_..." \
  https://interfacec-server.onrender.com/api/integrations/v1/thread-items/3685

10. Notes

  • Keys begin with pvi_. They're shown to you exactly once; rotate by deleting and re-creating.
  • The server stores only a hash of the key, never the key itself.
  • Integration writes show in the iOS app live (via WebSocket) the same as user-authored items, with the integration's name attached.
  • Need a different rate limit, a webhook, or a feature you don't see here? Email info@massiveinterface.com.
← Back to Peeved
Peeved
Privacy Terms Integrations App Store info@massiveinterface.com

© Massive Interface, LLC. All rights reserved.