GroupeZ Docs
APIv1 (legacy)

Authors

Legacy platform

This page documents the legacy v1 platform of groupez.dev (Laravel 8), which has been replaced. It is kept for reference and for plugins built against it. For the current behaviour, see Authors (v2).

A single endpoint returns a seller (an author of resources) by user id: their id, their Discord information and their name.

Common behaviour

ItemValue
Base URLhttps://groupez.dev/api/v1
AuthenticationNone
Rate limitapi limiter: 60 requests per minute, keyed by user id or, for anonymous callers, by IP address (see the note on the IP below)
Version headerNone (X-GroupeZ-Api-Version did not exist on v1)
CORSAccess-Control-Allow-Origin: * on api/*
Content typetext/html; charset=UTF-8, although the body is JSON (see the notes)

Endpoints

GET /api/v1/author/{id}

Looks up a user by id with User::findOrFail($id) and returns three keys.

Route nameAuthRate limit
api.v1.authorshowNonethrottle:api (60/min)

The route name has no dot between author and show: the route group was named author and the route show, with no separator.

Path parameters

NameTypeDescription
idintegerThe user id of the author. No route model binding and no pattern constraint; the value is passed straight to findOrFail.

Query / body parameters

None.

Responses

200 OK: the user exists.

The controller builds {"id", "discord", "name"} by hand. However, the discord column was dropped from the users table (migration 2021_10_10_203050_remove_discord_in_user), and $user->discord then resolved to the discord() relation (HasOne DiscordUser). So discord is either null or the whole discord_users row, serialized with every column, because the DiscordUser model declares no $hidden attributes.

Author without a linked Discord account:

{"id":42,"discord":null,"name":"Bobby"}

Author with a linked Discord account (placeholder values; keys in the column order of discord_users):

{
  "id": 42,
  "discord": {
    "id": 7,
    "user_id": 42,
    "discord_id": "987654321098765432",
    "access_token": "<oauth access token>",
    "refresh_token": "<oauth refresh token>",
    "expired_at": "2021-10-17T19:31:03.000000Z",
    "username": "Bobby",
    "email": "[email protected]",
    "discriminator": "1234",
    "avatar": "<avatar hash>",
    "is_valid": 1,
    "created_at": "2021-10-10T19:31:03.000000Z",
    "updated_at": "2021-10-10T19:31:03.000000Z"
  },
  "name": "Bobby"
}
KeyTypeDescription
idintegerThe user id.
discordobject | nullThe discord_users row linked to the user, or null.
discord.discord_idstringThe Discord user id (snowflake). This is the value plugins need to mention the seller.
namestringThe user's name.

Data exposure on the legacy endpoint

Because the full discord_users row was serialized, this public, unauthenticated endpoint returned the linked Discord account's OAuth access_token and refresh_token and the Discord email. This is one of the reasons the v2 platform reduces discord to the snowflake string. Do not rely on, store or log these fields.

404 Not Found: no user has this id. The ModelNotFoundException thrown by findOrFail goes through the default Laravel 8 exception handler (no customisation):

  • With Accept: application/json:

    {
      "message": "No query results for model [App\\Models\\User] 999"
    }
  • Without it: the site's HTML 404 page (resources/views/errors/404.blade.php).

With APP_DEBUG enabled, the JSON body would also carry exception, file, line and trace.

429 Too Many Requests: the api limiter tripped. Laravel 8 default: {"message": "Too Many Attempts."} with Accept: application/json, an HTML error page otherwise, plus Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers.

{
  "message": "Too Many Attempts."
}

There is no validation and no authorization, so the endpoint never answers 401, 403 or 422.

Example

curl -s https://groupez.dev/api/v1/author/42 \
  -H "Accept: application/json"

Notes

  • text/html content type. The controller returns json_encode([...]), a plain string, which Laravel sends with Content-Type: text/html; charset=UTF-8. Clients have to parse the body as JSON themselves. The encoding uses PHP defaults, so non-ASCII characters in name are escaped (é is sent as the six characters \u00e9) and slashes are escaped (\/).
  • Integer types. Whether id, discord.id, discord.user_id and discord.is_valid come out as JSON numbers or strings depends on the PHP version and PDO driver the legacy server ran (strings with MySQL emulated prepares before PHP 8.1). Treat them leniently. is_valid has no cast, so it is 1/0, not true/false.
  • Loose ids. Because {id} has no constraint, a non-numeric value is passed to the database as is. A value that matches no row answers 404. Mixed values such as 42abc may be coerced to a number by MySQL; this was never tested.
  • No account status filter. Banned or unverified users are returned like any other.
  • Which IP the limiter saw. In the api middleware group, throttle:api is listed before TrustCloudflare, and the global TrustProxies middleware declares no trusted proxy. The limiter therefore keyed on $request->ip() before the CF-Connecting-IP header was trusted. If the legacy site sat behind Cloudflare, that IP was a Cloudflare edge address, so unrelated callers could share one 60/min bucket. How production was actually fronted is not visible in the code. The API routes carry no session or auth middleware, so the user-id branch of the key was never used in practice.

Differences with v2

See Authors (v2). On v2, discord is the snowflake string or null, the response is application/json, a 404 answers {"message": "Not found."}, a 429 answers {"message": "Too many requests."}, the limit is 120 requests per minute, and every response carries X-GroupeZ-Api-Version: 1.

On this page

GroupeZ MCP

Documentation in your AI

MCP lets your AI search GroupeZ documentation and read current examples to help you configure your plugins.

Read-only · No documentation API key needed

1. Copy the server URL

Transport: HTTP (Streamable HTTP). Authentication: none. Your tool uses its own model and subscription limits.

  1. Open Plugins in ChatGPT on the web, then + → Add custom MCP server.
  2. Name it ‘GroupeZ Documentation’, paste the URL above and choose ‘No authentication’. Create and install the plugin.
  3. In a conversation, type @ and select the plugin before asking. If needed, enable search, get_page and list_pages in its settings.
Official setup guide

3. Ask a question

Use GroupeZ MCP to help me create a zMenu inventory. Search relevant pages, read the examples and cite your sources.
Other clients and troubleshooting

For clients using mcpServers, adapt this example to their format:

{
  "mcpServers": {
    "groupez-docs": {
      "url": ""
    }
  }
}

Available tools are search, get_page and list_pages. Refresh the connector’s tools after updates. If your AI cannot access them, check that the connector is enabled in the conversation and your account allows custom MCP servers. Opening the URL in a browser is not an MCP test.