# 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`.
