Skip to content
Blog

API Keys

Users can generate their own API keys through Tribe, then use those keys to authenticate against your backend. Tribe handles creation, listing, and deletion on the client side. Your backend verifies incoming keys by checking them against the Tribe API.

import { Tribe } from "@tribecloud/sdk";
const result = await tribe.createApiKey("My API Key", ["read", "write"]);
// result.id — key ID
// result.key — the full API key (only returned once!)
// result.name — "My API Key"
// result.scopes — ["read", "write"]
const keys = await tribe.listApiKeys();
// [{ id, name, keyPrefix, scopes, createdAt }]
await tribe.deleteApiKey(keyId);
function ApiKeyManager() {
const [keys, setKeys] = useState([]);
const [newKeyName, setNewKeyName] = useState("");
const [createdKey, setCreatedKey] = useState(null);
useEffect(() => {
tribe.listApiKeys().then(setKeys);
}, []);
const create = async () => {
const result = await tribe.createApiKey(newKeyName, ["read", "write"]);
setCreatedKey(result.key); // Show the full key — only chance!
setNewKeyName("");
setKeys(await tribe.listApiKeys());
};
const remove = async (id) => {
await tribe.deleteApiKey(id);
setKeys((k) => k.filter((key) => key.id !== id));
};
return (
<div>
<h2>API Keys</h2>
{createdKey && (
<div role="alert">
<strong>Your new API key:</strong>
<code>{createdKey}</code>
<p>Copy this now — you won't be able to see it again.</p>
<button onClick={() => setCreatedKey(null)}>Done</button>
</div>
)}
<div>
<input
value={newKeyName}
onChange={(e) => setNewKeyName(e.target.value)}
placeholder="Key name"
/>
<button onClick={create}>Create Key</button>
</div>
<ul>
{keys.map((key) => (
<li key={key.id}>
<strong>{key.name}</strong>{key.keyPrefix}...
<span> ({key.scopes.join(", ")})</span>
<button onClick={() => remove(key.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}

Scopes are arbitrary strings representing permission levels. Tribe stores them alongside each key, but the actual enforcement is up to your backend. Some common patterns:

  • ["read"] for read-only access
  • ["read", "write"] for full access
  • ["admin"] for administrative operations

When a user sends an API key to your backend, you can verify it by calling the Tribe verification endpoint. This checks the key against Tribe’s stored hashes and returns the associated user and scopes if the key is valid.

// POST https://api.tribe.utopian.build/api-key/verify
const response = await fetch("https://api.tribe.utopian.build/api-key/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
siteId: process.env.TRIBE_SITE_ID,
apiKey: keyFromRequest,
}),
});
const result = await response.json();
// result.valid — boolean
// result.userId — the user who owns this key
// result.scopes — ["read", "write"]

Here’s how you might wire this into Express middleware:

async function verifyApiKey(req, res, next) {
const apiKey = req.headers["x-api-key"];
if (!apiKey) return res.status(401).json({ error: "API key required" });
const result = await fetch("https://api.tribe.utopian.build/api-key/verify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
siteId: process.env.TRIBE_SITE_ID,
apiKey,
}),
}).then((r) => r.json());
if (!result.valid) return res.status(401).json({ error: "Invalid API key" });
req.apiKeyUser = result;
next();
}
// Use it on protected routes
app.get("/api/data", verifyApiKey, (req, res) => {
const { userId, scopes } = req.apiKeyUser;
if (!scopes.includes("read")) {
return res.status(403).json({ error: "Insufficient scope" });
}
// ... serve data
});