Support API

Native Help & Support screens

How a native app (iOS, Android, Flutter, React Native) builds its Help & Support screens on Gauge. The rule for every section: Gauge is the source of truth. Hours, staffing and who answers are…

How a native app (iOS, Android, Flutter, React Native) builds its Help & Support screens on Gauge. The rule for every section: Gauge is the source of truth. Hours, staffing and who answers are configured in the Gauge console. Your app reads them through your backend and stores none of it.

Your backend holds the API key (pulse_k_live_…). The app talks to your backend, and your backend talks to /v1/support. Never put the key in the app binary.

Status banner and help-centre search (status lane)

The status banner

import { GaugeSupport } from '@gauge/support';
const gauge = new GaugeSupport({ apiKey: process.env.GAUGE_KEY });

// GET /support/status in YOUR backend, proxied to the app:
const s = await gauge.projects.status(PROJECT_ID, { integration: 'customer' });
from gauge_support import GaugeSupport
gauge = GaugeSupport(os.environ["GAUGE_KEY"])
s = gauge.projects.status(PROJECT_ID, integration="customer")

How to render it (every value comes from Gauge, so show nothing you didn't get):

ShowFrom
"Our assistant answers" / "Our team answers" / "Assistant, with the team as backup"answering: ai_only / people_only / ai_with_people
"Team online" dotteamOnline, and availableCount.chat or .call for a count
"Open today 09:00–18:00"hours.todayRanges[].startLocal–endLocal (in hours.timezone). Empty = closed today.
"Opens Sunday 09:00"hours.nextOpenAt when hours.openNow is false
"Usually replies in about 4 min"typicalFirstReply.seconds. When it is null, show nothing. The reason is "not enough data".
Chat / Call / Ticket buttonschannels.chat.available, .call, .ticket. Disable or hide a channel and use reason for the copy (outside_business_hours, no_agents_online, queue_closed, not_offered, …).

When hours.configured is false, nothing is configured in Gauge, so don't print opening hours.

Freshness: Gauge caches the answer for at most 10 s. Poll while the screen is visible, every 30–60 s is plenty. The limit is 600 calls/min per API key, so cache in your backend if many users open the screen at once.

Help-centre search and article

const { results } = await gauge.projects.searchArticles(PROJECT_ID, query, { locale: 'ar', integration: 'customer' });
// results[i].highlights.title = [[start, end], …]: UTF-16 offsets into results[i].title
const article = await gauge.projects.getArticle(PROJECT_ID, results[0].slug, { locale: 'ar', integration: 'customer' });
// article.bodyHtml is sanitised. Render it in a WebView or an HTML text view. article.related lists more reading.
hits = gauge.projects.search_articles(PROJECT_ID, query, locale="ar", integration="customer")
article = gauge.projects.get_article(PROJECT_ID, hits["results"][0]["slug"], locale="ar", integration="customer")
  • Results match the web portal exactly: the same search, the same published, public articles.
  • Pass the integration your user belongs to (customer, salon). A shelf limited to salon appears only for integration: 'salon'. Shared articles appear for everyone.
  • Highlights are offsets, not HTML. Apply bold or a tint with NSAttributedString / SpannableString / TextSpan on the exact ranges. Swift's NSString and Kotlin both index in UTF-16, the same as the offsets.
  • Arabic works without extra code. Gauge folds alef forms, ta marbuta and diacritics on both sides.
  • getArticle takes the id or the slug. A draft, an internal article or another integration's article is a 404.
  • Limits: search 120/min, reads 600/min per API key. A 429 means back off.

Reference: api-s1.md §6.

Requests, threads and unread

Your backend acts as the signed-in user, named by externalRef (your user id, derived server-side from your session, never from the app's request body). Gauge refuses anything that is not that user's: another customer's ticket, chat or message is a 404, exactly like an id that does not exist.

"My requests" list

const page = await gauge.projects.listTickets(PROJECT_ID, { externalRef: user.id, statusGroup: 'open' });
// page.rows[i]: subject, statusGroup, lastReply.preview, lastActivityAt, sla.state, context, unread
page = gauge.projects.list_tickets(PROJECT_ID, external_ref=user.id, status_group="open")
ShowFrom
Tabs Open / Waiting for you / ResolvedstatusGroup: open / waiting_on_customer / resolved (filter with statusGroup=)
Last message linelastReply.preview (≤140 chars, never an internal note), lastReply.authorType (agent/ai = "from the team")
TimelastActivityAt
Dot / count on the rowunread
"About booking BK-1042"context (null when the request is not about a record)

Opening a request from a record ("Get help")

await gauge.projects.createTicket(PROJECT_ID, {
  subject, body, requesterExternalRef: user.id,
  integration: 'customer',                    // or 'salon' in the salon app
  context: { type: 'booking', id: booking.id } // booking | transaction | invoice | voucher | payout
});

Check that the user owns or can see that record before you send it. Gauge stores your id as-is, shows it to agents ("About: Booking BK-1042") and returns it on the list and the thread, but never looks it up.

The thread

const t = await gauge.projects.getThread(PROJECT_ID, ticketId, user.id);
// t.messages: customer-visible only, oldest first; t.attachments[i].downloadState; t.readOnly
await gauge.projects.reply(PROJECT_ID, ticketId, user.id, text);
await gauge.projects.uploadAttachment(PROJECT_ID, ticketId, user.id, { filename, contentType, contentBase64 });
  • The reply is the portal's reply: written as that customer, it reopens a waiting or resolved request and never emails the customer.
  • readOnly: true (blocked, deactivated, no access): hide the composer. Writes answer 403 customer_read_only.
  • download.url is short-lived. Fetch it when the user taps, never cache it. scanning = not openable yet.

Unread badge

const { total } = await gauge.projects.unread(PROJECT_ID, user.id); // header bell
await gauge.projects.markRead(PROJECT_ID, user.id, { scope: 'ticket', ticketId }); // when the thread is shown
// { scope: 'chat', chatId } after showing a chat, { scope: 'all' } for "mark all read"
gauge.projects.unread(PROJECT_ID, user.id)["total"]
gauge.projects.mark_read(PROJECT_ID, user.id, scope="ticket", ticket_id=ticket_id)
  • Unread counts only the team's messages (agent, AI) after the user's read position, over their requests and their chats. Their own messages and internal notes never count.
  • The read position is shared with the web portal. Opening the request there clears it in the app too.
  • Marks only move forward. Pass throughMessageId (the last message you showed) if new messages may have arrived while the screen was loading. Without it the whole thread is marked read.
  • Instead of polling, subscribe to the support.customer.unread_changed webhook (ids + counts, cause: read | team_message) and push payload.total to the user's devices. Keep the highest payload.version.

Reference: api-s1.md §7.

On this page