Facia Docs
For AI Open Facia
Build on Facia Beta

Recipes

Each recipe builds on the skeleton from Your first app: verify the token on your server, then call facia(call, body).

#Coin raffle

Permissions: Take coins, Give coins, Post in groups, Send notifications.

js
// Enter: 20 coins per ticket. The ref makes a double-tap one charge.
app.post("/api/enter", async (req, res) => {
  const c = client(req);
  const ticket = `${raffle.id}:${c.sub}:${req.body.n}`;
  const r = await facia("coins.charge", { sub: c.sub, amount: 20, reason: `Raffle ticket #${req.body.n}`, ref: `ticket:${ticket}` });
  if (!r.ok) return res.json(r); // e.g. { error: "short", balance: 5 }
  await db.tickets.insert({ biz: c.biz, sub: c.sub, alias: c.alias, raffle: raffle.id });
  res.json(r);
});

// Draw (on a timer): pay the winner, tell the group, ping the winner.
async function draw(raffle) {
  const tickets = await db.tickets.find({ raffle: raffle.id });
  const win = tickets[crypto.randomInt(tickets.length)];
  const pot = tickets.length * 20;
  await facia("coins.pay", { sub: win.sub, amount: pot, reason: "Raffle win", ref: `win:${raffle.id}` });
  await facia("group.post", { group: raffle.groupId, text: `๐ŸŽŸ ${win.alias} won the raffle: ${pot} coins!` });
  await facia("push.send", { sub: win.sub, title: "You won the raffle!", text: `${pot} coins are yours.` });
}

#Scored game with a leaderboard

Permissions: Post scores, Give badges.

js
app.post("/api/score", async (req, res) => {
  const c = client(req);
  const score = validateRun(req.body.run); // re-check the run on the server; never trust a raw score from the browser
  const r = await facia("board.score", { sub: c.sub, board: "runner", value: score });
  if (r.ok && r.rank === 1) await facia("badge.give", { sub: c.sub, badge: "runner-no1", label: "Runner #1", icon: "๐Ÿƒ" });
  res.json(r);
});

The board starts fresh on the business's leaderboard period (day, week or month). best is the client's best this period.

#Daily check-in mission

Permissions: Count mission events.

The business creates a Mission block with Done when โ†’ An app reports it, event checkin, target 5, reward 100 coins. Your app reports each check-in:

js
app.post("/api/checkin", async (req, res) => {
  const c = client(req);
  const day = new Date().toISOString().slice(0, 10);
  if (await db.checkins.exists({ biz: c.biz, sub: c.sub, day })) return res.json({ ok: true, already: true });
  await db.checkins.insert({ biz: c.biz, sub: c.sub, day });
  res.json(await facia("mission.event", { sub: c.sub, event: "checkin" }));
});

Facia counts it, and pays the reward when the client reaches the target. Your app never touches coins.

#Members-only shop (coins)

Permissions: See a balance, Take coins. Audience: Verified members.

js
app.post("/api/buy", async (req, res) => {
  const c = client(req);
  const item = catalog[req.body.item];
  const order = await db.orders.create({ biz: c.biz, sub: c.sub, item: item.id, status: "pending" });
  const r = await facia("coins.charge", { sub: c.sub, amount: item.price, reason: item.name, ref: `order:${order.id}` });
  await db.orders.update(order.id, { status: r.ok ? "paid" : "failed" });
  res.json(r);
});

Coins are not money. For real-money products, send the client to your own checkout outside Facia. Never take card details inside the frame.

#Staff tool

Set Who sees it to Your team only. The token has role: "team" and alias = the staff member's display name. Use it for an internal dashboard, an order lookup or a stock board. You get the business's look and sign-in for free.

js
if (c.role !== "team") return res.status(403).end();

#Handling rate limits and retries

js
async function faciaRetry(call, body, tries = 3) {
  for (let i = 0; i < tries; i++) {
    const res = await fetch(`${process.env.FACIA_API}/${call}`, { method: "POST",
      headers: { Authorization: `Bearer ${process.env.FACIA_APP_SECRET}`, "Content-Type": "application/json" },
      body: JSON.stringify(body) });
    const data = await res.json();
    if (res.status === 429) { await new Promise((r) => setTimeout(r, data.retryAfterMs ?? 1000)); continue; }
    if (res.status >= 500) { await new Promise((r) => setTimeout(r, 1000 * 2 ** i)); continue; }
    return data;
  }
  throw new Error(`Facia ${call} failed`);
}

Coin calls are safe to retry with the same ref.

View as Markdown