Connect Loops to anything

Loops' public REST API is language-agnostic. Websites, mobile apps, Slack, Discord, GitHub Actions, even cron jobs — any HTTP client with a bearer token works.

REST APIWebhooksJS WidgetBot templatesSelf-hosted

3 minutes

Quick start

  1. 1
    Sign in / sign up, then create your board (you can make a separate board per product).
  2. 2
    your board's Settings → API Keys page, create a secret or publishable key. Each board has its own keys.
  3. 3
    Try the API with that board's key via cURL:
    bash
    curl -H "Authorization: Bearer loop_sk_..." \
      https://getloops.co/api/v1/posts

Multiple products

Multiple boards

Create a separate board for each product/app. Boards are fully independent — each has its own posts, members, AI key and API keys. Got 5 apps? Make 5 boards and embed each one with its own key.

  1. 1
    Create one board per product: getloops.co/acme-web, getloops.co/acme-mobile, …
  2. 2
    Grab each board's own publishable (widget) and secret (REST) key from its Settings → API Keys.
  3. 3
    Embed the widget — or call the REST API — in each app with that board's key. Each app only ever sees its own board.

Example: two products, two separate boards

html
<!-- Product A → board "acme-web" -->
<div id="loop-web"></div>
<script src="https://getloops.co/loop-widget.js"
        data-key="loop_pk_WEB_..."
        data-target="#loop-web"></script>

<!-- Product B → board "acme-mobile" -->
<div id="loop-mobile"></div>
<script src="https://getloops.co/loop-widget.js"
        data-key="loop_pk_MOBILE_..."
        data-target="#loop-mobile"></script>

HTML

Embed widget

Drop a feedback board into any HTML page with a single line. Vanilla JS, ~8KB, no dependencies.

html
<div id="loop-board"></div>
<script src="https://getloops.co/loop-widget.js"
        data-key="loop_pk_..."
        data-host="https://getloops.co"
        data-target="#loop-board"
        data-theme="light"
        data-locale="tr"
        data-user-id="user_42"></script>  <!-- optional: your logged-in user -->

data-key: publishable key (loop_pk_…). Never put a secret key in the browser.

data-theme: light or dark.

data-locale: tr or en.

data-target: CSS selector where the widget mounts.

data-user-id: your logged-in user's id — ties votes/posts to that user (deduped across devices). Omit for anonymous guests.

Users

Identify users

Tie votes and posts to the real user in your app. An identified user gets one vote even across devices; without it, visitors are counted as guests (browser/IP).

Web (widget)

Pass your logged-in user's id to the widget script:

html
<script src="https://getloops.co/loop-widget.js"
        data-key="loop_pk_..."
        data-user-id="user_42"
        data-target="#loop-board"></script>

API / server

On the REST API, send external_user_id (or the X-Loop-External-User header for votes) on every vote/post. Same id = same user.

bash
curl -X POST -H "Authorization: Bearer loop_sk_..." \
  -H "X-Loop-External-User: user_42" \
  https://getloops.co/api/v1/posts/POST_ID/vote

Tip: use your own database user id as external_user_id. For anonymous visitors, generate a persistent id in the browser and always send the same one.

iOS / Android

Mobile

Two ways to use Loops in a native app:

1) WebView (fastest)

Open your board (or the widget) in a WebView — works fully with no code. For a logged-in user, append ?u=<user_id> and pass it to the widget's data-user-id.

swift
// iOS — WKWebView
let url = URL(string: "https://getloops.co/acme")!
webView.load(URLRequest(url: url))

// Android — WebView
webView.loadUrl("https://getloops.co/acme")

2) Native + REST API

Build your own native screen and call the REST API for listing/voting/posting, sending your app's user id as external_user_id (never embed a secret key in the app — proxy through your backend).

Node + discord.js

Discord bot

Minimal bot that posts to Loops via the /feedback slash command.

bash
npm i discord.js
# Create a bot + applications.commands scope in the Discord Developer Portal
js
import {
  Client, GatewayIntentBits, REST, Routes,
  SlashCommandBuilder,
} from "discord.js";

const cmd = new SlashCommandBuilder()
  .setName("feedback")
  .setDescription("Send feedback to Loops")
  .addStringOption(o => o.setName("text").setDescription("Your idea").setRequired(true));

const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
await rest.put(
  Routes.applicationCommands(process.env.DISCORD_APP_ID),
  { body: [cmd.toJSON()] }
);

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on("interactionCreate", async (i) => {
  if (!i.isChatInputCommand() || i.commandName !== "feedback") return;
  const text = i.options.getString("text", true);
  const r = await fetch(`${process.env.LOOP_HOST}/api/v1/posts`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.LOOP_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      title: text.slice(0, 140),
      source: "discord",
      external_user_id: `dc_${i.user.id}`,
      tag: "discord",
    }),
  });
  await i.reply({ content: r.ok ? "✓ Added." : "❌ Error.", ephemeral: true });
});

client.login(process.env.DISCORD_TOKEN);