Facia Docs
For AI Open Facia
Build on Facia Beta

Partner API

Your app's server calls Facia to change things inside the business: charge or pay coins, post a score, give a badge, post in a group, send a notification, or count toward a mission. Each call is scoped to one business, needs a permission the business ticked, and is written to the business's activity log.

#Basics

http
POST https://site.facia.chat/partner/v1/<call>
Authorization: Bearer fas_…
Content-Type: application/json

{ …body… }
  • Base address: the FACIA_API value from Copy as .env in HQ.
  • Auth: your app secret (fas_ + 43 characters) as a Bearer token. Server-side only.
  • Body: JSON. An empty body counts as {}.
  • Response: always JSON, Cache-Control: no-store. Success has "ok": true. Failure has "ok": false (or just error) with an error code.
  • CORS is open (*), but never call it from the browser: that would expose your secret.

#Calling it

js
async function facia(call, body) {
  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));
  return data;
}
bash
curl -X POST "$FACIA_API/coins.balance" \
  -H "Authorization: Bearer $FACIA_APP_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"sub":"cl_7f3aQ2xY9pLm0KdE"}'

#Permissions

The business ticks these under What it may do when registering your app. Each call needs its own.

Permission (HQ label)Call
Take coinscoins.charge
Give coinscoins.pay
See a balancecoins.balance
Post scoresboard.score
Give badgesbadge.give
Post in groups (+ which groups)group.post
Send notificationspush.send
Count mission eventsmission.event

#Calls

#coins.charge

Takes coins from a client. Fails if they don't have enough.

json
{ "sub": "cl_…", "amount": 50, "reason": "Raffle entry", "ref": "raffle-123:cl_…" }
json
{ "ok": true, "balance": 190, "amount": 50, "ref": "raffle-123:cl_…" }

#coins.pay

Gives coins to a client. They get a coins card in their support chat.

json
{ "sub": "cl_…", "amount": 100, "reason": "Trivia winner", "ref": "trivia-2026-10-17:cl_…" }
json
{ "ok": true, "balance": 340, "amount": 100, "ref": "trivia-2026-10-17:cl_…" }

For both coin calls:

  • amount: a whole number above 0.
  • reason: up to 120 characters, shown to the client and in the activity log.
  • ref: required, up to 100 characters. It makes the call idempotent: the same ref twice is one charge, and the second call gets the first answer back with "repeat": true. Refs are shared between coins.charge and coins.pay for your app at that business. Make them unique per action, e.g. raffle-123:cl_….
  • Coins work only while the business has Client coins on, and only for Verified and VIP clients.

#coins.balance

json
{ "sub": "cl_…" }
json
{ "ok": true, "balance": 240, "member": true }

balance is 0 when coins are off or the client isn't a member. Not logged.

#board.score

Posts a score to a named board on the business's leaderboard. The best value per client per period is kept.

json
{ "sub": "cl_…", "board": "trivia", "value": 870 }
json
{ "ok": true, "board": "trivia", "value": 870, "best": 920, "rank": 3, "period": "week" }
  • board: up to 40 characters. value: a number, rounded to 2 decimals.
  • Needs the business's Leaderboard on. Only clients who can be ranked (not staff, not Spam) count.
  • rank counts up to 100 places above them.

#badge.give

Gives a badge, shown on the client's membership. Each badge key is given once.

json
{ "sub": "cl_…", "badge": "trivia-champ", "label": "Trivia champ", "icon": "🏆" }
json
{ "ok": true, "badge": "trivia-champ", "given": true }

badge (up to 40) is lowercased, and anything other than letters, numbers, - and _ becomes -. label (up to 40) and icon (an emoji, up to 8 characters) are what's shown. given is false if they already had it.

#group.post

Posts a message in a group or channel, as the business. Only to groups the business chose for your app (up to 20).

json
{ "group": "<chat id>", "text": "🎉 This week's trivia champion is Silver Fox!" }
json
{ "ok": true, "messageId": "…" }

text up to 2,000 characters. Photos aren't supported yet.

#push.send

Sends a notification to a client's devices (up to 10).

json
{ "sub": "cl_…", "title": "Your raffle ticket won!", "text": "Tap to claim your prize.", "open": "app" }
json
{ "ok": true, "devices": 2 }

title up to 80 characters and text up to 160 (at least one of them). open: "app" (default, opens your tab) or "chat" (opens their support chat). Extra limit: 120 an hour per app per business, bursts of 20.

#mission.event

Counts toward the business's missions whose trigger is An app reports it with the same event name.

json
{ "sub": "cl_…", "event": "trivia_played", "n": 1 }
json
{ "ok": true, "event": "trivia_played", "counted": true }

event up to 40 characters, lowercased. n is a whole number of 1 or more (default 1). counted is false when no running mission uses that event for this client.

#Errors

HTTPerrorMeans
400bad_jsonThe body isn't JSON
400ref_required, amount_invalidCoin calls: missing ref, or amount not a whole number above 0
400board_required, value_invalidboard.score input
400badge_requiredbadge.give input
400text_requiredgroup.post with no text, or push.send with no title and no text
400event_required, n_invalidmission.event input
401unauthorizedWrong secret, the app is off, the business turned apps off, or Facia blocked the app. Deliberately all the same
403permission_missingThe business didn't tick that permission (permission says which)
403group_not_allowedNot one of the groups the business chose
404unknown_callNo such call
404client_unknownNo client with that sub at this business
409shortNot enough coins (balance included)
409coins_off, leaderboard_offThe business has that feature off
409client_not_memberA guest (no coins). tier included
409client_not_rankedStaff or Spam can't be on the board
409group_unavailableThat group can't be posted in right now (for example it was deleted)
409refusedA rule inside Facia said no (message says why)
429rate_limitedSlow down. Wait retryAfterMs
500refusedSomething went wrong on Facia's side. Retry later (coin calls are safe to retry with the same ref)
503–Facia is temporarily unavailable

#Limits

LimitValue
Calls1,200 an hour per app per business, bursts of 120
Push120 an hour per app per business, bursts of 20
Groups per app20
Apps per business20

#Activity log

Every write shows in HQ → Activity with your app's name: app.charge, app.pay, app.score, app.badge, app.post, app.push, and app.mission (only when it counted). The business can see exactly what your app did.

View as Markdown