FriendPath API Documentation

Programmatic access to the same engine that powers friendpath.arbastro.com. Two public APIs: the Scan API resolves the connection path between two Roblox users; the Famous Users API returns a curated list of well-known Roblox accounts. The Scan API is metered (with a generous Suite-member free tier); the Famous Users API is completely free.

Introduction

FriendPath traces degrees of separation between any two Roblox players using bidirectional BFS through Roblox's public friend graph. Maximum depth is 20. Most queries between active players resolve in 2–4 degrees and complete in under 5 seconds. Edge cases (very inactive accounts, banned users, hidden friend lists) may take up to 30 seconds or return not_found.

Two APIs, two access models: The Scan API requires an arbastro account + API key (because it's expensive to run). The Famous Users API is free for everyone, no key, no signup.

Authentication

The Scan API authenticates via API keys passed as a Bearer token in the Authorization header. API keys begin with ak_live_ and are tied to your arbastro account. Each key is shown to you exactly once at creation; we only store its SHA-256 hash. You can revoke a key at any time from your settings.

The Famous Users API requires no authentication.

Step 1 — Create an arbastro account

FriendPath shares its account system with arbastro.com. If you don't already have an arbastro account, head to arbastro.com/signup and complete the 5-step signup (email + password, link Discord, link Roblox, pick username, recommendations). The whole thing takes about 3 minutes.

Already signed up? Just log in at the top of friendpath.arbastro.com using the same credentials.

Step 2 — Generate an API key

  1. Sign in at arbastro.com/login
  2. Go to Settings → API keys
  3. Click Generate, give it a label (e.g. "my discord bot")
  4. Copy the key immediately — it's shown once. Lost keys must be regenerated.
  5. If you don't have arbastro Suite, click Add card first to enable metered billing

Step 3 (optional) — Get arbastro Suite for free daily quota

arbastro Suite is a $4.99/month (or $4.17/month yearly) subscription that includes 50 free FriendPath Scan API calls per day, ad-free FriendPath usage, priority support, and subsidiary-giveaway eligibility. New users get a 30-day free trial.

Without Suite, you can still use the API — you just pay per call from request #1. With Suite, the first 50 calls per day each UTC day are free; calls 51+ are billed at the same metered rate.

Scan API — Overview

Resolve any two Roblox usernames or numeric user IDs and receive their connection path through the friend graph.

Endpoint

GET https://arbastro.com/api/v1/friendpath/path

Parameters

ParamTypeRequiredDescription
fromstringYesRoblox username or numeric ID. Numbers are treated as user IDs; non-numbers as usernames.
tostringYesSame as from — the other end of the path.
maxDepthnumberNoMaximum BFS depth. Default 20. Lower values fail faster on disconnected pairs.

Headers

HeaderRequiredDescription
AuthorizationYesBearer ak_live_... — your API key

Response shape

Returns a JSON object describing the path between the two users:

// 200 OK — path found
{
  "from": { "input": "Builderman", "userId": 156 },
  "to":   { "input": "david.baszucki", "userId": 5208617297 },
  "result": {
    "type": "done",
    "degrees": 2,
    "visited": 14328,
    "path": [
      { "id": 156,        "username": "Builderman" },
      { "id": 119358574,  "username": "someMutual" },
      { "id": 5208617297, "username": "david.baszucki" }
    ]
  }
}

Code examples

Click a tab below to view the example for your language.

curl "https://arbastro.com/api/v1/friendpath/path?from=Builderman&to=david.baszucki" \
  -H "Authorization: Bearer ak_live_YOUR_KEY"
// Browser or Node 18+ (uses fetch)
const res = await fetch(
  "https://arbastro.com/api/v1/friendpath/path?from=Builderman&to=david.baszucki",
  { "headers": { "Authorization": "Bearer ak_live_YOUR_KEY" } }
);
const data = await res.json();
// data.result.degrees === 2
// data.result.path === [{id, username}, ...]
console.log(`degrees: ${data.result.degrees}`);
type PathResult = {
  from: { input: string; userId: number };
  to:   { input: string; userId: number };
  result: {
    type: "done" | "not_found" | "error";
    degrees?: number;
    visited?: number;
    path?: { id: number; username: string }[];
  };
};

async function scanPath(from: string, to: string): Promise<PathResult> {
  const u = new URL("https://arbastro.com/api/v1/friendpath/path");
  u.searchParams.set("from", from);
  u.searchParams.set("to", to);
  const r = await fetch(u, {
    headers: { Authorization: `Bearer ${process.env.FRIENDPATH_KEY}` }
  });
  if (!r.ok) throw new Error(`scan failed: ${r.status}`);
  return r.json();
}
import os, requests

def scan_path(a: str, b: str) -> dict:
    key = os.environ["FRIENDPATH_KEY"]
    r = requests.get(
        "https://arbastro.com/api/v1/friendpath/path",
        params={"from": a, "to": b},
        headers={"Authorization": f"Bearer {key}"},
        timeout=60,
    )
    r.raise_for_status()
    return r.json()

data = scan_path("Builderman", "david.baszucki")
print(f"degrees: {data['result']['degrees']}")
--!strict
-- ServerScript — drop in ServerScriptService
-- Game Settings → Security → Allow HTTP Requests must be ON

local HttpService = game:GetService("HttpService")

local FRIENDPATH_KEY: string = "ak_live_YOUR_KEY"
local API_BASE: string = "https://arbastro.com/api/v1/friendpath/path"

type PathNode = { Id: number, Username: string }

type ScanResult = {
    Found: boolean,
    Degrees: number?,
    Path: { PathNode }?,
    Error: string?,
}

local function ScanFriendPath(FromUser: string, ToUser: string): ScanResult
    local Url = API_BASE
        .. "?from=" .. HttpService:UrlEncode(FromUser)
        .. "&to="   .. HttpService:UrlEncode(ToUser)

    local Ok, Response = pcall(function()
        return HttpService:RequestAsync({
            Url = Url,
            Method = "GET",
            Headers = {
                ["Authorization"] = "Bearer " .. FRIENDPATH_KEY,
            },
        })
    end)

    if not Ok or not Response.Success then
        return { Found = false, Error = "http error" } :: ScanResult
    end

    local Body = HttpService:JSONDecode(Response.Body)
    local Result = Body.result

    if Result.type ~= "done" then
        return { Found = false, Error = Result.type } :: ScanResult
    end

    local Path: { PathNode } = {}
    for _, Node in ipairs(Result.path) do
        table.insert(Path, { Id = Node.id, Username = Node.username })
    end

    return {
        Found = true,
        Degrees = Result.degrees,
        Path = Path,
    } :: ScanResult
end

-- Usage:
local Result = ScanFriendPath("Builderman", "david.baszucki")
if Result.Found then
    print("degrees:", Result.Degrees)
end
// discord.js v14 — slash command /friendpath
const { SlashCommandBuilder } = require("discord.js");

module.exports = {
  data: new SlashCommandBuilder()
    .setName("friendpath")
    .setDescription("degrees of separation between two Roblox users")
    .addStringOption(o => o.setName("from").setRequired(true))
    .addStringOption(o => o.setName("to").setRequired(true)),

  async execute(i) {
    await i.deferReply();
    const u = new URL("https://arbastro.com/api/v1/friendpath/path");
    u.searchParams.set("from", i.options.getString("from"));
    u.searchParams.set("to",   i.options.getString("to"));
    const r = await fetch(u, {
      headers: { Authorization: `Bearer ${process.env.FRIENDPATH_KEY}` }
    });
    const data = await r.json();
    if (data.result?.type !== "done") return i.editReply("no path found 😕");
    await i.editReply(
      `**${data.result.degrees} degrees** apart\n` +
      data.result.path.map(n => `• ${n.username}`).join("\n")
    );
  }
};
package friendpath

import (
    "encoding/json"
    "fmt"
    "net/http"
    "net/url"
    "os"
)

type PathResult struct {
    Result struct {
        Type    string
        Degrees int
        Path    []struct{ Id int64; Username string }
    } `json:"result"`
}

func Scan(from, to string) (*PathResult, error) {
    u, _ := url.Parse("https://arbastro.com/api/v1/friendpath/path")
    q := u.Query()
    q.Set("from", from); q.Set("to", to); u.RawQuery = q.Encode()
    req, _ := http.NewRequest("GET", u.String(), nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("FRIENDPATH_KEY"))
    resp, err := http.DefaultClient.Do(req)
    if err != nil { return nil, err }
    defer resp.Body.Close()
    out := &PathResult{}
    return out, json.NewDecoder(resp.Body).Decode(out)
}

Live runner

Test the Scan API directly from this page. You'll need an API key — paste yours below or grab one.

Try the Scan API


      

Errors

StatusBody errorCause
400from and to are requiredMissing query param
401missing or invalid api key / api key revokedBearer token bad
402payment_method_requiredOut of free quota and no card on file
404—Username didn't resolve to a user
502upstream friendpath unreachableBackend overloaded — retry

Billing & rate limits

Each successful Scan API call costs $0.005. arbastro Suite members get 50 free calls per UTC day before metering kicks in. Quota resets at 00:00 UTC.

Concrete rate limits per API key:

Exceed any of these and you'll get a 429 Too Many Requests with a Retry-After header indicating how many seconds to wait. Failed calls (4xx/5xx) do not count against your quota or bill — only successful 200 responses are billed.

For high-volume use cases beyond the daily ceiling (10k+ calls/day), email [email protected] for a custom quota.

Reliability, SLAs, and retries

FriendPath runs on a dedicated Oracle Cloud VM (not a shared-tier hobby host) with auto-restart via systemd, Cloudflare DDoS protection, and an origin TLS cert that doesn't expire until 2041. See the live status page for current uptime metrics across 24h / 7d / 30d / 90d windows.

Reliability targets:

Retry policy: the API is idempotent for a given (from, to) pair. Retrying a request with the same parameters is always safe and won't double-bill — only the final successful 200 response is charged. Recommended retry strategy:

Idempotency keys: not required — the API doesn't have side effects beyond billing. If you need to be extra-safe with charges, supply your own X-Idempotency-Key header (any string) and we'll deduplicate identical requests received within 10 minutes.

Webhooks for long-running scans

For scans you expect to take a while (5+ hop paths, low-activity accounts), you can supply a webhook URL and we'll POST the result there instead of holding the HTTP connection open. Add &webhook=YOUR_HTTPS_URL to the request and the API responds immediately with 202 Accepted and a scanId. When the scan finishes, we POST the same JSON shape as the synchronous response to your webhook URL.

Webhook delivery guarantees:

Status & monitoring: live status at friendpath.arbastro.com/status. Machine-readable health at /api/healthz. Detailed JSON with uptime stats at /api/status — suitable for plugging into your own monitoring (UptimeRobot, Better Stack, Pingdom).

Famous Users API — Overview

Returns a curated list of "famous" Roblox users, including arbastro and subsidiary group members and creators from the Rolimons gamelist. Updated daily. Used internally by FriendPath's dice-roll button. Free for anyone, no API key, no signup.

Endpoint

GET https://friendpath.arbastro.com/api/famous-users

Response shape

Returns an array of user objects:

// 200 OK
[
  {
    "id": 156,
    "name": "Builderman",
    "displayName": "Builderman",
    "followers": 5_182_341,
    "friends": 96
  },
  { "id": 5208617297, "name": "david.baszucki", ... },
  ...
]

The list contains roughly 200–500 entries depending on the day's gamelist data. Order is approximately by follower count, with the hardcoded "always-included" accounts first.

Code examples

curl "https://friendpath.arbastro.com/api/famous-users"
const famous = await fetch("https://friendpath.arbastro.com/api/famous-users")
  .then(r => r.json());
// Pick a random one
const picked = famous[Math.floor(Math.random() * famous.length)];
console.log(`random famous: ${picked.name}`);
import requests, random

famous = requests.get("https://friendpath.arbastro.com/api/famous-users").json()
picked = random.choice(famous)
print(f"random famous: {picked['name']}")
--!strict
local HttpService = game:GetService("HttpService")

type FamousUser = {
    Id: number,
    Name: string,
    DisplayName: string?,
    Followers: number?,
    Friends: number?,
}

local function FetchFamousUsers(): { FamousUser }
    local RawJson: string = HttpService:GetAsync(
        "https://friendpath.arbastro.com/api/famous-users"
    )
    local Decoded = HttpService:JSONDecode(RawJson)
    local List: { FamousUser } = {}
    for _, U in ipairs(Decoded) do
        table.insert(List, {
            Id          = U.id,
            Name        = U.name,
            DisplayName = U.displayName,
            Followers   = U.followers,
            Friends     = U.friends,
        })
    end
    return List
end

-- Usage:
local Famous = FetchFamousUsers()
local Picked = Famous[math.random(#Famous)]
print("random famous:", Picked.Name)

Live runner

Hit "Fetch" — no key needed.

Try the Famous Users API


      

How the list is built & fair use

The list is assembled at server boot from three sources, deduplicated, and refreshed daily:

The endpoint serves a cached list — please don't poll faster than once per 5 minutes. Heavy abuse may result in IP-level rate limiting. No authentication required for normal usage.

Embed Widget

The FriendPath embed widget lets you drop a tiny "Are you friends with X?" search box on any other website with a single iframe. Useful for Roblox-creator personal sites, fan wikis, Discord landing pages, or anywhere you want a quick path-to-celebrity check without rebuilding the UI.

The widget is self-contained: it loads its own minimal CSS, prefills a target username from the URL, accepts the visitor's username in an input, and on submit opens a new tab to FriendPath with both names pre-filled and the search auto-started.

iframe snippet

Replace USERNAME with the Roblox account you want visitors to check their distance to:

<iframe
  src="https://friendpath.arbastro.com/embed/david.baszucki"
  width="360"
  height="240"
  frameborder="0"
  loading="lazy"
  title="FriendPath — Are you friends with David Baszucki?">
</iframe>

The widget responds to whatever width/height you give the iframe. The recommended minimum is 320×220. The embed URL is https://friendpath.arbastro.com/embed/<username> — only URL-safe characters (letters, digits, underscore, period, hyphen) are accepted; everything else returns 400.

You can also link to the main FriendPath site with ?from=X&to=Y query params. The page will pre-fill both fields and automatically start the search:

https://friendpath.arbastro.com/?from=YourUsername&to=david.baszucki

Either parameter is optional. If only one is supplied, that input is pre-filled and the user finishes the other manually. If both are supplied, the search starts automatically after a short delay (~300ms, to let the autocomplete and other modules initialize).

Live preview

This is exactly what the iframe renders:

What the widget does NOT do

Privacy & embedding policy

The widget sets a permissive Content-Security-Policy: frame-ancestors * header so any site can embed it. It doesn't track visitors — no cookies, no analytics. The only network request the widget makes is when a user clicks Find Path, which opens a new tab to the main site.

Backlinks from embed instances help FriendPath's SEO. If you embed it on your own site, leaving the small "Powered by FriendPath" footer link visible is appreciated but not required.

Type definitions reference

TypeScript

export type FriendPathScanResult = {
  from: { input: string; userId: number };
  to:   { input: string; userId: number };
  result: {
    type: "done" | "not_found" | "error";
    degrees?: number;
    visited?: number;
    path?: FriendPathNode[];
    message?: string;
  };
};

export type FriendPathNode = {
  id: number;
  username: string;
};

export type FamousUser = {
  id: number;
  name: string;
  displayName: string;
  followers?: number;
  friends?: number;
};

Changelog

Get help

Stuck? Three ways to reach us: