SPECTRA API
Base URLhttps://spectra.dreamforger.xyz/api

Spectra Seller API

Manage whitelists and blacklists from code, and check players from your own loader.

For AI agents

The whole API above as one Markdown file. Paste it into your AI agent's context, or point the agent at https://spectra.dreamforger.xyz/api/docs/seller-api.md.

# Spectra Seller API

Base URL: `https://spectra.dreamforger.xyz/api`

This document describes the public Spectra seller API. It is written to be pasted into an AI agent's context. All paths below are relative to the base URL.

## Overview

The Spectra seller API lets you manage your own scripts' whitelist and blacklist from code, and lets your in-game loader ask whether a player is whitelisted or blacklisted.

| | |
|---|---|
| Base URL | `https://spectra.dreamforger.xyz/api` |
| Format | JSON requests and responses (`Content-Type: application/json` on request bodies) |
| Management auth | `X-API-Key` header (your seller API key) |
| Check API auth | `token` query parameter (a per-script check token), no header |
| Plan | Adding to a whitelist/blacklist and the Check API need a Premium or Lifetime plan. Listing and removing work on any plan. |
| Rate limit | 60 requests per minute on `/seller/*` |

Two separate APIs live here. Use the **Whitelist** and **Blacklist** sections from your own backend or tooling. Use the **Check API** section from inside a Roblox script.

```bash
# list your scripts
curl -H "X-API-Key: YOUR_API_KEY" https://spectra.dreamforger.xyz/api/seller/scripts
```

## Authentication

**Seller API key** (management). Send it on every `/seller/*` request:

```http
X-API-Key: spak_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

Keys are created while you are signed in to the dashboard, with `POST /seller/api-keys` (session login required; body `{"label": "my bot"}`). The full key is shown **once** in that response and only a hash is stored, so save it immediately. List your keys with `GET /seller/api-keys` and revoke one with `DELETE /seller/api-keys/{key_id}`. Those three routes use your dashboard session, not an API key.

**Check token** (Check API). A separate, read-only credential scoped to one script. Get or rotate it in Discord with the `/checktoken` command (`script_id`, optional `rotate`). It is meant to be embedded in a script players can read, so it can only answer whitelist/blacklist questions and nothing else.

> Never put your `X-API-Key` in a script players can read. Only the check token is safe to embed.

## Scripts

Every whitelist and blacklist route is keyed by the numeric script id. This is the only route that lists those ids.

### GET /seller/scripts

List your scripts. Auth: X-API-Key.

Example:
```bash
curl -H "X-API-Key: YOUR_API_KEY" \
  https://spectra.dreamforger.xyz/api/seller/scripts
```

Response `200`:
```json
[
  {
    "id": 54,
    "name": "My Script",
    "tier_used": "premium",
    "enabled": true,
    "is_free": false,
    "anti_dump": true,
    "two_stage": false,
    "created_at": "2026-09-01T12:00:00Z",
    "updated_at": "2026-10-09T09:30:00Z"
  }
]
```

- Only the most useful fields are shown above; more per-script settings are returned. The secret `vm_token` is never included.

## Whitelist

Players on a script's whitelist are allowed to run it. Usernames are matched case-insensitively.

### GET /seller/whitelist/{script_id}

List whitelist entries. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Example:
```bash
curl -H "X-API-Key: YOUR_API_KEY" \
  https://spectra.dreamforger.xyz/api/seller/whitelist/54
```

Response `200`:
```json
[
  {
    "id": 17,
    "script_name": "My Script",
    "roblox_username": "SomePlayer",
    "revoked": false,
    "created_at": "2026-10-10T18:22:04.113Z",
    "hwid": null,
    "last_used_at": null
  }
]
```

- Includes revoked entries (`revoked: true`). `hwid` is filled in after the player first runs the script.

### POST /seller/whitelist/{script_id}

Add one username. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_username": "SomePlayer"
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_username":"SomePlayer"}' \
  https://spectra.dreamforger.xyz/api/seller/whitelist/54
```

Response `201`:
```json
{
  "id": 17,
  "script_name": "My Script",
  "roblox_username": "SomePlayer",
  "revoked": false,
  "created_at": "2026-10-10T18:22:04.113Z"
}
```

- Re-adding a previously revoked username un-revokes it.
- Premium/Lifetime only (403 otherwise).

### POST /seller/whitelist/{script_id}/bulk

Add many usernames. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_usernames": [
    "SomePlayer",
    "AnotherPlayer"
  ]
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_usernames":["SomePlayer","AnotherPlayer"]}' \
  https://spectra.dreamforger.xyz/api/seller/whitelist/54/bulk
```

Response `201`:
```json
{
  "added": [
    {
      "id": 17,
      "script_name": "My Script",
      "roblox_username": "SomePlayer",
      "revoked": false,
      "created_at": "2026-10-10T18:22:04.113Z"
    }
  ],
  "count": 1
}
```

- Up to 200 usernames per request. Blanks and case-insensitive duplicates are dropped; 400 if nothing valid remains.
- Premium/Lifetime only.

### POST /seller/whitelist/{script_id}/bulk-remove

Remove many usernames. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_usernames": [
    "SomePlayer"
  ]
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_usernames":["SomePlayer"]}' \
  https://spectra.dreamforger.xyz/api/seller/whitelist/54/bulk-remove
```

Response `200`:
```json
{
  "revoked": 1
}
```

- Same 200-username cap and cleaning as bulk add.

### DELETE /seller/whitelist/{script_id}/{whitelist_id}

Remove one entry by id. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |
| `whitelist_id` | path | integer | The entry's `id` from the list route. |

Example:
```bash
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
  https://spectra.dreamforger.xyz/api/seller/whitelist/54/17
```

Response `204`:
```json
(empty body)
```

- Revokes the entry. 404 if the id isn't an entry on that script.

## Blacklist

Blacklisted players are blocked from running the script, even if they would otherwise qualify.

### GET /seller/blacklist/{script_id}

List blacklist entries. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Example:
```bash
curl -H "X-API-Key: YOUR_API_KEY" \
  https://spectra.dreamforger.xyz/api/seller/blacklist/54
```

Response `200`:
```json
[
  {
    "id": 17,
    "script_name": "My Script",
    "roblox_username": "SomePlayer",
    "created_at": "2026-10-10T18:22:04.113Z"
  }
]
```

### POST /seller/blacklist/{script_id}

Add one username. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_username": "BadActor"
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_username":"BadActor"}' \
  https://spectra.dreamforger.xyz/api/seller/blacklist/54
```

Response `201`:
```json
{
  "id": 17,
  "script_name": "My Script",
  "roblox_username": "SomePlayer",
  "created_at": "2026-10-10T18:22:04.113Z"
}
```

- Adding someone already blacklisted just returns their existing entry.
- Premium/Lifetime only.

### POST /seller/blacklist/{script_id}/bulk

Add many usernames. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_usernames": [
    "BadActor",
    "AnotherOne"
  ]
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_usernames":["BadActor","AnotherOne"]}' \
  https://spectra.dreamforger.xyz/api/seller/blacklist/54/bulk
```

Response `201`:
```json
{
  "added": [
    {
      "id": 17,
      "script_name": "My Script",
      "roblox_username": "SomePlayer",
      "created_at": "2026-10-10T18:22:04.113Z"
    }
  ],
  "count": 1
}
```

- Up to 200 usernames per request.
- Premium/Lifetime only.

### POST /seller/blacklist/{script_id}/bulk-remove

Remove many usernames. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |

Request body:
```json
{
  "roblox_usernames": [
    "BadActor"
  ]
}
```

Example:
```bash
curl -X POST -H "X-API-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
  -d '{"roblox_usernames":["BadActor"]}' \
  https://spectra.dreamforger.xyz/api/seller/blacklist/54/bulk-remove
```

Response `200`:
```json
{
  "removed": 1
}
```

### DELETE /seller/blacklist/{script_id}/{blacklist_id}

Remove one entry by id. Auth: X-API-Key.


| Name | In | Type | Description |
|---|---|---|---|
| `script_id` | path | integer | Numeric id of one of your scripts (from `GET /seller/scripts`). |
| `blacklist_id` | path | integer | The entry's `id` from the list route. |

Example:
```bash
curl -X DELETE -H "X-API-Key: YOUR_API_KEY" \
  https://spectra.dreamforger.xyz/api/seller/blacklist/54/17
```

Response `204`:
```json
(empty body)
```

- 404 if the id isn't an entry on that script.

## Check API

For your own in-game loader: ask whether a player is whitelisted or blacklisted for one script. Read-only, authenticated by a check token instead of an API key.

### GET /check/status

Check one player. Auth: token query param (no header).


| Name | In | Type | Description |
|---|---|---|---|
| `token` | query | string | Your script's check token (Discord `/checktoken`). |
| `user` | query | string | Roblox username to look up (max 100 chars, case-insensitive). |

Example:
```bash
curl "https://spectra.dreamforger.xyz/api/check/status?token=YOUR_CHECK_TOKEN&user=SomePlayer"
```

Response `200`:
```json
{
  "whitelisted": true,
  "blacklisted": false
}
```

- **Always HTTP 200.** A bad/rotated token, missing params, an owner below Premium, and being rate-limited all return `{"whitelisted": null, "blacklisted": null}`, on purpose, so the API can't be used to probe which tokens exist.
- `null` means "couldn't verify right now". It is **not** the same as `false`. Decide in your loader whether unknown should fail open or closed.
- Rate limit: 20 requests per 10 seconds per token. Call once per player join, not in a loop.

**Ready-made Lua client.** Instead of writing the HTTP calls yourself, load the official client from inside your Roblox script:

```lua
local SpecProtect = loadstring(game:HttpGet(
    "https://spectra.dreamforger.xyz/api/loader/specprotect.lua"
))("YOUR_CHECK_TOKEN")

local name = game.Players.LocalPlayer.Name

if SpecProtect.is_blacklisted(name) then
    return -- blocked
end
if not SpecProtect.is_whitelisted(name) then
    return -- not allowed
end

-- raw result, if you want to handle null yourself:
local status = SpecProtect.check_status(name)
-- status.whitelisted / status.blacklisted can be true, false or nil
```

| Function | Unknown result (nil) is treated as |
|---|---|
| `is_whitelisted(user)` | `false` (fails closed) |
| `is_blacklisted(user)` | `false` (fails open) |
| `check_status(user)` | returns the raw table, nil fields preserved |

> Honest limit: this proves the caller holds your check token, not that they are an untampered Roblox client. If the code around the check lives in a plain unprotected script, someone can delete the check. It is a convenience for custom loaders, not a replacement for protecting the script itself.

## Errors

Errors from `/seller/*` return JSON like `{"detail": "Script not found."}` with these status codes:

| Status | Meaning |
|---|---|
| `401` | `X-API-Key` header missing, invalid, or revoked. |
| `403` | Your plan doesn't include this action (adding to whitelist/blacklist needs Premium or Lifetime). |
| `404` | Script id isn't yours, or the entry id doesn't exist on that script. |
| `400` | Bulk request had no valid usernames after cleaning. |
| `422` | Request body failed validation (wrong field name/type, empty or oversized list, username over 100 chars). |
| `429` | Rate limit exceeded (60/min on `/seller/*`). Back off and retry. |

`/check/status` is the exception: it never returns an error status. See the Check API section.

A machine-readable OpenAPI schema of everything on this page is available at `/openapi.json`.