Integrations
The Integrations feature lets your server connect an external service (your stats backend, a killfeed, a price ticker, a CRM lookup) to Discord without writing code. You set up the connection in the dashboard, MARVIN fetches the data on demand, and it becomes available to Custom Commands, Tickets, and Welcome messages.
Every step below is also available from the Start from template dropdown on the Integrations page in your dashboard — pick a preset and the form pre-fills. You still enter your own values (URL, key) before saving.
What this is
- Connection — the base URL + auth credentials for one API.
- Endpoint — a specific route on that connection, with its method (GET/POST), path template, optional body template, and a response map that tells the bot which JSON fields to surface.
- Field mapping — a JSONPath-lite expression (e.g.
data[0].kills) that pulls a value out of the response and exposes it under a friendly name likekills. - Binding — a custom command or ticket template that interpolates the mapped value via
{api.<connection>.<field>}.
Credentials never round-trip after save — the dashboard only stores them encrypted; the bot reads + decrypts them locally. Edits require re-entering the credential.
1. Create a connection
- Open /dashboard/<guild>/api-connections.
- In the left rail, click New connection.
- Pick a template from the Start from template dropdown. Three presets exist today:
- Blank — empty form. Use when no preset matches.
- Custom API (generic JSON) — sane defaults for the typical case: JSON-over-HTTPS with an
X-API-Keyheader. - Killfeed (worked example) — pre-fills the structure used in this doc's example below.
- Replace the placeholder values:
- name — any short alphanumeric label. This is how you'll reference the connection in command bindings (
{api.killfeed.kills}← thatkillfeedtoken). - base_url — full
https://URL of your API host. The bot enforces HTTPS unlessallow_httpis explicitly enabled. - auth_type — one of
none,api_key_header,bearer,basic, orquery_key. Pick the one your API uses. - credentials — header name + key, bearer token, etc. depending on auth_type.
- name — any short alphanumeric label. This is how you'll reference the connection in command bindings (
- Click Create connection.
2. Add an endpoint
A connection by itself does nothing — it's just credentials + a base URL. You need at least one endpoint.
- Select the connection you just created from the left rail.
- Click Add endpoint.
- Fill in:
- name — short identifier like
player_stats. Used in bindings as the second token:{api.<connection>.<endpoint>.<field>}. - method —
GETfor reads,POSTfor sending JSON. - path_template — path appended to base_url. Use
{var}tokens for runtime interpolation. The built-inguild_id,user_id, andchannel_idare always available; custom command args fill the rest. - body_template (POST only) — JSON body with the same
{var}interpolation. Values are JSON-escaped automatically. - response_map — one row per output field. key is the friendly name; JSONPath is where to find it in the response.
- name — short identifier like
3. Test it
Use the Test button on the endpoint. The dashboard sends the request via the bot (so it uses the real credentials + the SSRF allowlist), then shows you the raw response + the field map output side-by-side. If a JSONPath doesn't resolve, you'll see (unmapped) next to that field — fix the path and re-test.
4. Use the data
Open the Custom commands page. Create or edit a command. In the response body, use the field picker (the inline blue token icon) or type the placeholder manually:
/!stats — "{api.killfeed.player_stats.kills} kills, {api.killfeed.player_stats.deaths} deaths"When the command fires, the bot calls the endpoint, applies the response map, and substitutes the values. Failed fetches surface as a friendly error message in the channel rather than silently dropping the command.
Worked example: Killfeed
Suppose you run a Rust/Squad/Tarkov server and your stats backend exposes a killfeed at https://killfeed.example.com/feed?guild=<id> with an API key in an X-API-Key header.
Sample response:
{
"guild_id": "1156016923855827074",
"recent": [
{ "killer": "Player-A", "victim": "Player-B", "weapon": "AK-47", "distance": 142 },
{ "killer": "Player-C", "victim": "Player-D", "weapon": "M4", "distance": 87 }
],
"totals": { "kills_24h": 412, "active_players": 38 }
}Connection setup (pick the Killfeed (worked example) preset, then swap in real values):
- name:
killfeed - base_url:
https://killfeed.example.com - auth_type:
api_key_header - header_name:
X-API-Key - key: your API key
Endpoint setup:
- name:
recent - method:
GET - path_template:
/feed?guild={guild_id} - response_map:
last_killer→recent[0].killerlast_victim→recent[0].victimlast_weapon→recent[0].weaponkills_24h→totals.kills_24hactive→totals.active_players
- cache_seconds:
30(don't hammer the API on every command)
Custom command binding:
/!lastkill →
"{api.killfeed.recent.last_killer} killed {api.killfeed.recent.last_victim} with {api.killfeed.recent.last_weapon} ({api.killfeed.recent.active} players active right now)"Now /!lastkill in any channel returns the live killfeed snapshot. Same plumbing works for leaderboards, player lookups, ticket enrichment, etc. — just add more endpoints to the same connection or create a new connection per backend.
Tips + gotchas
- HTTPS-only by default. If your API is plain HTTP (rare; don't use plain HTTP in prod), toggle
allow_httpon the connection. - SSRF guard. The bot blocks fetches to private IPs (10.x, 192.168.x, 169.254.x, etc.) and metadata-service hostnames. If your API is on a private network, expose it through a proper tunnel.
- Cache aggressively.
cache_secondsdefaults to 0 — every command fires a fresh request. Bump it to 30-300s on hot commands so you don't blow your API's rate limit. - Credential rotation. Re-open the connection, re-enter the credential, save. The encrypted column is overwritten in place.
- Field picker beats typing. Use the blue token icon in the custom-command editor to click-pick a mapped field instead of typing
{api.foo.bar.baz}from memory.