Saved Sep 23, 2026 by @enviglo: Settings tabs, your own Discord bot, custom domains, and giving products by Discord ID or email
- Enviglo's API lets your game, bot or own tools work with your store: record purchases, check who owns what, run codes, read customers and more. This guide gets you from a new key to a working request, and covers what you need to know before building on it.+ Enviglo's API lets your game, bot or own tools work with your store: record purchases, give products, check who owns what, run codes, read customers and more. This guide gets you from a new key to a working request, and covers what you need to know before building on it.## Before you start…```That's trimmed: each product carries more fields. Add `?status=draft`, `?status=archived` or `?status=all` to see other products. A product's `id` is what other endpoints mean by `productId`.++ ## Give someone a product++ With the **Grant and revoke licenses** scope, `POST /licenses` gives someone one of your products, the same as **Grant license** in your dashboard. Name the product with `productId`, and the person with exactly one of these:++ | Field | Who it names |+ |---|---|+ | `username` | Their Enviglo username. |+ | `customer` | A customer, by the `customer` key from `GET /customers`. |+ | `discordId` | Their Discord user ID. |+ | `email` | Their email address. |+ | `robloxId` | Their Roblox user ID. On its own, this records a purchase made in your game. Send `"source": "GRANT"` with it to give instead. |++ Except with `robloxId`, you can also send:++ - `days`: for a subscription, how many days to give, from 1 to 3,650. The product's own length unless you say.+ - `note`: a private note of up to 500 characters, kept in the license's history.++ ```bash+ curl -X POST "https://enviglo.com/api/v1/licenses" \+ -H "Authorization: Bearer YOUR_API_KEY" \+ -H "Content-Type: application/json" \+ -d '{ "email": "buyer@example.com", "productId": "clx7a1b2c", "note": "Giveaway winner" }'+ ```++ To give by Roblox ID, the body is:++ ```json+ { "robloxId": "99999999001", "productId": "clx7a1b2c", "source": "GRANT" }+ ```++ It answers 201 with `"created": true` and the license, or 200 with `"renewed": true` when it added days to a subscription they already hold. What a grant gives depends on the product's license type, as in the dashboard: see [Grant a license](/wiki/manage-licenses#grant-a-license).++ ### Someone who hasn't joined yet++ If the Discord or Roblox account is linked on Enviglo, or someone has confirmed that email address there, the product goes straight to their account. Anyone else has it waiting for them, and shows up under **Customers** in your dashboard like any other customer:++ - **Discord ID:** if the product has a Discord role and they're in your server, the role is theirs straight away. The product is theirs once they sign in to Enviglo with that Discord account, or link it to an account they already have.+ - **Email address:** it's theirs once they sign in with that address and it's confirmed. An account that has the address but hasn't confirmed it doesn't get it. Enviglo never shows the address back to your store.+ - **Roblox ID:** your game can check it by that Roblox ID straight away, and it's theirs once they link that Roblox account.++ When a bot asks `GET /discord/entitlements?discordUserId=` which roles a member should have, the answer counts what you gave their Discord ID too, with `"linked": false` until they sign in with it or link it.++ Only a `robloxId` can record a Roblox purchase, so `"source": "ROBLOX_PURCHASE"` with anyone else is refused. So is sending none of the five, or more than one: "Pass exactly one of robloxId, discordId, email, username or customer."## Read errors…
Enviglo's API lets your game, bot or own tools work with your store: record purchases, give products, check who owns what, run codes, read customers and more. This guide gets you from a new key to a working request, and covers what you need to know before building on it.
You need an API key for your store. See API keys. Keep it on a server: never in a web page, a LocalScript or a public repository.
https://enviglo.com/api/v1. The API keys card in Settings → Integrations shows it too.Authorization: Bearer YOUR_API_KEY, or in an x-api-key header.Content-Type: application/json.GET /store answers with the store your key belongs to and the scopes the key has. Any key can call it, so it's the quickest way to check a key works:
curl "https://enviglo.com/api/v1/store" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"store": {
"id": "cls9x0",
"slug": "your-store",
"name": "Your Store",
"badges": ["VERIFIED"],
"robloxPlaceId"
_count is the store's published products, active licenses and followers.
The same call from a Roblox server script:
-- A Script in ServerScriptService. Server only: it holds your API key.
local HttpService = game:GetService("HttpService")
local sent, response = pcall(function()
return HttpService:RequestAsync({
Url = "https://enviglo.com/api/v1/store",
Method = "GET",
Headers = { ["Authorization"] = "Bearer YOUR_API_KEY" },
})
end)
if not sent then
warn("Couldn't reach Enviglo:", response)
elseif not response.Success then
warn("Enviglo answered HTTP " .. response.StatusCode .. ": " .. response.Body)
else
local data = HttpService:JSONDecode(response.Body)
print("This key belongs to " .. data.store.name)
end
Roblox needs Allow HTTP Requests switched on for this, under Game Settings → Security in Studio.
With the Read products scope, GET /products lists your published products, sorted by name, with the Roblox and Discord IDs a game or bot needs:
curl "https://enviglo.com/api/v1/products" \
-H "Authorization: Bearer YOUR_API_KEY"
{
"products": [
{
"id": "clx7a1b2c",
"slug": "advanced-admin",
"name": "Advanced Admin",
"status": "PUBLISHED",
"licenseType": "SINGLE",
"robuxPrice": 400,
"stock": null,
"robloxProductId": "1234567890",
"robloxGamePassId": null,
"discordRoleId": null,
"activeLicenses": 318,
"url": "/s/your-store/advanced-admin"
}
]
}
That's trimmed: each product carries more fields. Add ?status=draft, ?status=archived or ?status=all to see other products. A product's id is what other endpoints mean by productId.
With the Grant and revoke licenses scope, POST /licenses gives someone one of your products, the same as Grant license in your dashboard. Name the product with productId, and the person with exactly one of these:
| Field | Who it names |
|---|---|
username | Their Enviglo username. |
customer | A customer, by the customer key from GET /customers. |
discordId | Their Discord user ID. |
email | Their email address. |
robloxId | Their Roblox user ID. On its own, this records a purchase made in your game. Send "source": "GRANT" with it to give instead. |
Except with robloxId, you can also send:
days: for a subscription, how many days to give, from 1 to 3,650. The product's own length unless you say.note: a private note of up to 500 characters, kept in the license's history.curl -X POST "https://enviglo.com/api/v1/licenses" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "email": "buyer@example.com", "productId": "clx7a1b2c", "note": "Giveaway winner" }'
To give by Roblox ID, the body is:
{ "robloxId": "99999999001", "productId": "clx7a1b2c", "source": "GRANT" }
It answers 201 with "created": true and the license, or 200 with "renewed": true when it added days to a subscription they already hold. What a grant gives depends on the product's license type, as in the dashboard: see Grant a license.
If the Discord or Roblox account is linked on Enviglo, or someone has confirmed that email address there, the product goes straight to their account. Anyone else has it waiting for them, and shows up under Customers in your dashboard like any other customer:
When a bot asks GET /discord/entitlements?discordUserId= which roles a member should have, the answer counts what you gave their Discord ID too, with "linked": false until they sign in with it or link it.
Only a robloxId can record a Roblox purchase, so "source": "ROBLOX_PURCHASE" with anyone else is refused. So is sending none of the five, or more than one: "Pass exactly one of robloxId, discordId, email, username or customer."
When a request goes wrong, the API answers with a status code and a JSON body saying what happened:
{ "error": "This key does not have the licenses:write scope." }
| Status | What it means | For example |
|---|---|---|
| 400 | Something in the request is wrong: a missing or malformed parameter, or a body that isn't JSON. | "Provide a numeric robloxId." |
| 401 | The key is missing, mistyped or revoked. | "Invalid or revoked API key." |
| 403 | The key doesn't have the scope this needs, or the store isn't open. | "This store is not active." |
| 404 | It isn't in this store. A key only ever sees its own store. | "Product not found in this store." |
| 409 | It can't happen as things stand, like publishing a product that isn't ready. | "Not ready to publish: …" |
| 429 | Too many requests. Wait a moment and try again. | "Too many licenses recorded in the last minute. Wait a moment and retry." |
Lists that can run long come a page at a time: licenses, codes and requests. Ask for up to 100 at once with limit; you get 50 if you don't ask. When there's more, the answer carries nextCursor. Send it back as cursor for the next page. A nextCursor of null means you have everything.
curl "https://enviglo.com/api/v1/licenses?status=active&limit=100" \
-H "Authorization: Bearer YOUR_API_KEY"
curl "https://enviglo.com/api/v1/licenses?status=active&limit=100&cursor=clx8f2k0a0001" \
-H "Authorization: Bearer YOUR_API_KEY"
Licenses and codes come newest first, and requests most recently updated first, so each page moves on to older items.
Customers are paged by number instead: page=1, page=2 and on, with nextPage in the answer saying what's next, or null when there isn't one.
Each key can:
POST /licenses, counting repeats and grants;POST /codes;GET /customers and GET /stats.Past that it answers 429 until the time is up. The lookups a game makes when players join, like checking licenses, have no fixed limit, but ask once per player per server, not on a loop.