> ## Documentation Index
> Fetch the complete documentation index at: https://docs.joinmarkt.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Merchant Discord

> Read and update the store Discord bot integration with a Bearer API key.

All routes require:

```txt theme={null}
Authorization: Bearer mk_live_<your_key>
```

The caller must be able to access that store. A key with store scope `SELECTED` must include that store. Access mode `FULL` skips the scope list and still requires dashboard permission `configureStorefront`. Access mode `RESTRICTED` must include the scope on the route and that same permission. `discord:write` also satisfies `discord:read`.

```http theme={null}
GET   /api/merchant/v1/stores/{storeId}/discord
PATCH /api/merchant/v1/stores/{storeId}/discord
PUT   /api/merchant/v1/stores/{storeId}/discord
```

`PUT` calls the same handler as `PATCH`.

`clientSecret` and `botToken` are never returned. The response uses `hasClientSecret` and `hasBotToken`.

## Discord settings

`GET`, `PATCH`, and `PUT` return this object. `PATCH` and `PUT` also fill `applicationSync`. `GET` sets `applicationSync` to `null`.

When no integration is saved, `configured` and `enabled` are `false`, the id fields are `null`, both arrays are empty, `showCheckoutLink` is `true`, and `inviteUrl` is `null`. The three URL fields are still set.

**storeId** `string`

Store record id.

**configured** `boolean`

`true` when an integration row exists.

**enabled** `boolean`

Stored flag. A successful `PATCH` or `PUT` saves `enabled` as `true`.

**clientId** `string | null`

Discord application client id, or `null`.

**hasClientSecret** `boolean`

`true` when a client secret is stored.

**hasBotToken** `boolean`

`true` when a bot token is stored.

**applicationId** `string | null`

Discord application id, or `null`.

**publicKey** `string | null`

Discord application public key, or `null`.

**connectedGuildIds** `string[]`

Guild ids the bot is in.

**authorizedDiscordUserIds** `string[]`

Discord user snowflakes allowed to manage the bot.

**customerRoleId** `string | null`

Role snowflake given to customers, or `null`.

**customerInviteUrl** `string | null`

Discord invite URL shown to customers, or `null`.

**showCheckoutLink** `boolean`

Whether checkout shows the Discord link.

**commandsSyncedAt** `string | null`

ISO time of the last command sync, or `null`.

**redirectUri** `string`

`{storePublicUrl}/discord/callback`

**buyerLinkRedirectUri** `string`

`{storePublicUrl}/discord/link/callback`

**interactionsUrl** `string`

`{dashboardUrl}/api/discord/interactions`

**inviteUrl** `string | null`

Bot invite URL when `clientId` and a bot token are both stored. Otherwise `null`.

**applicationSync** `object | null`

`null` on `GET`. On `PATCH` and `PUT`, the result of pushing the application description, OAuth redirects, and (on a public HTTPS dashboard) the interactions endpoint to Discord.

**properties**

**attempted** `boolean`

`true` after a save.

**ok** `boolean`

`true` when Discord accepted the update.

**message** `string | null`

Sync result text.

```json theme={null}
{
  "storeId": "STORE_ID",
  "configured": true,
  "enabled": true,
  "clientId": "123456789012345678",
  "hasClientSecret": true,
  "hasBotToken": true,
  "applicationId": "123456789012345678",
  "publicKey": "abcdef",
  "connectedGuildIds": ["223456789012345678"],
  "authorizedDiscordUserIds": ["323456789012345678"],
  "customerRoleId": "423456789012345678",
  "customerInviteUrl": "https://discord.gg/joinmarkt",
  "showCheckoutLink": true,
  "commandsSyncedAt": null,
  "redirectUri": "https://acme.joinmarkt.com/discord/callback",
  "buyerLinkRedirectUri": "https://acme.joinmarkt.com/discord/link/callback",
  "interactionsUrl": "https://dash.joinmarkt.com/api/discord/interactions",
  "inviteUrl": "https://discord.com/api/oauth2/authorize?client_id=123456789012345678&permissions=268437504&response_type=code&scope=bot+applications.commands&redirect_uri=https%3A%2F%2Facme.joinmarkt.com%2Fdiscord%2Fcallback&state=STATE",
  "applicationSync": null
}
```

`inviteUrl` uses that path when a client id and bot token are stored. The query is `client_id`, `permissions` (default `268437504`), `response_type=code`, `scope` `bot applications.commands`, `redirect_uri` (the store `redirectUri`), and `state`.

## Get Discord settings

```http theme={null}
GET /api/merchant/v1/stores/{storeId}/discord
```

Scope: `discord:read`. No body and no query parameters.

**storeId** `string` *required*

Store record id.

```bash theme={null}
curl https://dash.joinmarkt.com/api/merchant/v1/stores/STORE_ID/discord \
  -H "Authorization: Bearer mk_live_<your_key>"
```

## Update Discord settings

```http theme={null}
PATCH /api/merchant/v1/stores/{storeId}/discord
PUT   /api/merchant/v1/stores/{storeId}/discord
```

Scope: `discord:write`. JSON object. The path `storeId` is the store that is saved. A `storeId` property in the body is discarded.

Client id, client secret, and bot token must all be present after the save, using stored values for any secret you omit. Saving sets `enabled` to `true`. Omit `clientSecret` or `botToken` to keep the stored secret. A string of only `•` characters keeps it as well.

The bot token is checked with Discord. The client id must match the application id or that application's bot user id. Guild ids are refreshed from Discord when that call succeeds, and commands are synced per guild.

**storeId** `string` *required*

Store record id.

**clientId** `string`

Discord client id, trimmed, 5–64 characters.

**clientSecret** `string`

Client secret, trimmed, 1–256 characters.

**botToken** `string`

Bot token, trimmed, 1–256 characters.

**enabled** `boolean`

Accepted on the body. The stored integration is saved with `enabled` true.

**authorizedDiscordUserIds** `string[]`

Up to 50 snowflakes. Each snowflake is 5–32 digits. Replaces the stored list when sent.

**customerRoleId** `string | null`

Role snowflake, or `null` to clear it.

**customerInviteUrl** `string | null`

Discord invite URL, max 500 characters, or `null`. An empty string clears it. The host must be `discord.gg`, `discord.com`, `discordapp.com`, or a host ending in `.discord.com`.

**showCheckoutLink** `boolean`

Checkout Discord link. Omitted values keep the stored flag, or `true` when nothing is stored yet.

```json theme={null}
{
  "clientId": "123456789012345678",
  "clientSecret": "secret",
  "botToken": "token",
  "authorizedDiscordUserIds": ["323456789012345678"],
  "customerRoleId": "423456789012345678",
  "customerInviteUrl": "https://discord.gg/joinmarkt",
  "showCheckoutLink": true
}
```

```bash theme={null}
curl -X PATCH https://dash.joinmarkt.com/api/merchant/v1/stores/STORE_ID/discord \
  -H "Authorization: Bearer mk_live_<your_key>" \
  -H "Content-Type: application/json" \
  -d "{\"clientId\":\"123456789012345678\",\"clientSecret\":\"secret\",\"botToken\":\"token\"}"
```

`400` messages from this handler:

| `error` | When |
| - | - |
| `Invalid JSON` | Body is not JSON |
| `JSON body must be an object` | Body is not a JSON object |
| `No updatable Discord fields provided` | Object has no fields after `storeId` is removed |
| `Client ID, Client Secret, and Bot Token are required` | The three credentials are still incomplete |
| `Client ID does not match the Discord application for this bot token` | Client id matches neither the application id nor the bot user id |
| Discord `message`, or `Discord API <status>` | The bot-token lookup threw an `Error`. That covers a Discord rejection and a network failure. The HTTP status of this route stays `400`. |
| `Invalid bot token` | The bot-token lookup threw a value that is not an `Error`. |
| `Customer invite must be a Discord invite URL` | `customerInviteUrl` host is not a Discord invite host |
| `Must be a Discord snowflake ID` | A user id or role id is not 5–32 digits |

Zod messages are joined with `; `.

## Errors

Failed responses use `{ "success": false, "error": "...", "code": "..." }`. A `500` also includes `errorId`.

| Status | Code | When |
| - | - | - |
| 400 | `BAD_REQUEST` | Invalid JSON, empty update, credentials, Discord validation, or field validation |
| 401 | `UNAUTHORIZED` | Missing, invalid, or revoked token. `error` is `Unauthorized`. Expired keys use `API key expired`. |
| 403 | `FORBIDDEN` | Key cannot access this store, or the scope / `configureStorefront` is missing. `error` is `Forbidden`. |
| 404 | `NOT_FOUND` | `Store not found` |
| 429 | `RATE_LIMITED` | More than 120 requests in 60 seconds for this client IP, on the shared `merchant-api` bucket. The limit is checked before the key. `error` is `Too many requests`. |
| 500 | `INTERNAL_ERROR` | Unexpected failure. `error` is `Failed to load Discord settings` or `Failed to update Discord settings`. |

## Related

* [Merchant notifications](https://docs.joinmarkt.com/api-reference/merchant-notifications.md)
* [API keys](https://docs.joinmarkt.com/developers/api-keys.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.