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.
3 minutes
Quick start
- 1Sign in / sign up, then create your board (you can make a separate board per product).
- 2your board's Settings → API Keys page, create a secret or publishable key. Each board has its own keys.
- 3Try 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.
- 1Create one board per product: getloops.co/acme-web, getloops.co/acme-mobile, …
- 2Grab each board's own publishable (widget) and secret (REST) key from its Settings → API Keys.
- 3Embed 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
<!-- 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.
<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:
<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.
curl -X POST -H "Authorization: Bearer loop_sk_..." \
-H "X-Loop-External-User: user_42" \
https://getloops.co/api/v1/posts/POST_ID/voteTip: 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.
// 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.
npm i discord.js
# Create a bot + applications.commands scope in the Discord Developer Portalimport {
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);