API Documentation
REST API for team storage, random generation, and authenticated Showdown helpers
The endpoints below are the supported public compatibility contract. The OpenAPI document is the machine-readable source for schemas and status codes; crob.at's UI-only routes are intentionally excluded. API discovery metadata is available through APIs.json.
Endpoints
GET /api/team/:slug
Retrieve a team by its slug.
Example:
curl https://crob.at/api/team/abc123
const response = await fetch('https://crob.at/api/team/abc123');
const data = await response.json();
console.log(data);
import requests
response = requests.get('https://crob.at/api/team/abc123')
data = response.json()
print(data)
Response:
{
"slug": "abc123",
"name": "My Team",
"author": "username",
"description": "Short notes about how the team plays.",
"image": "https://crob.at/og/abc123.png",
"source_url": null,
"created_at": "2026-07-21 10:30:00",
"views": 12,
"is_multi": false,
"teams": [{
"format": "gen9ou",
"name": "My Team",
"paste": "Garchomp @ Choice Scarf\n..."
}]
}
GET /api/team/:slug/:teamSlug
Retrieve one team from a multi-team paste. Use the child slug from its share URL, such as /abc123/rain-offense.
Example:
curl https://crob.at/api/team/abc123/rain-offense
Response:
{
"paste_slug": "abc123",
"slug": "rain-offense",
"format": "gen9ou",
"name": "Rain Offense",
"paste": "Pelipper @ Damp Rock\\n...",
"url": "https://crob.at/abc123/rain-offense",
"image": "https://crob.at/og/abc123.png?team=rain-offense"
}
POST /api/team
Create a new team.
Request:
{
"name": "My Team Name",
"author": "username",
"description": "Short notes about how the team plays.",
"public": false,
"teams": [{
"name": "My Team",
"format": "gen9ou",
"paste": "Garchomp @ Choice Scarf\nAbility: Rough Skin\n..."
}]
}
teams must contain at least one entry. Optional names, author, and description are truncated to their storage limits. A supplied format must be supported by Pokémon Showdown.
Example:
curl -X POST https://crob.at/api/team \
-H "Content-Type: application/json" \
-d '{"name": "My Team", "description": "Fast offense with a Scarf cleaner.", "teams": [{"paste": "Garchomp @ Choice Scarf\n..."}]}'
const response = await fetch('https://crob.at/api/team', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'My Team',
description: 'Fast offense with a Scarf cleaner.',
teams: [{paste: 'Garchomp @ Choice Scarf\n...'}]
})
});
const {slug, url} = await response.json();
console.log(`Created: ${url}`);
import requests
response = requests.post('https://crob.at/api/team', json={
'name': 'My Team',
'description': 'Fast offense with a Scarf cleaner.',
'teams': [{'paste': 'Garchomp @ Choice Scarf\n...'}]
})
data = response.json()
print(f"Created: {data['url']}")
Response:
{
"slug": "abc123",
"url": "https://crob.at/abc123",
"image": "https://crob.at/og/abc123.png"
}
GET /api/random-team/:format
Generate a random competitive team for a Smogon format. Returns the Showdown export and rendered card HTML used by the site UI. Generation does not create a saved team.
Example:
curl https://crob.at/api/random-team/gen9ou
const response = await fetch('https://crob.at/api/random-team/gen9ou');
const data = await response.json();
console.log(data.teamText);
import requests
response = requests.get('https://crob.at/api/random-team/gen9ou')
data = response.json()
print(data['teamText'])
Response:
{
"teamText": "Gholdengo @ Leftovers\n...",
"statsDate": "2026-03",
"cardsHtml": "<div class=\"pokemon-grid\">...</div>"
}
POST /api/random-team/:format/save
Create an unlisted permanent URL for a generated team. Send the teamText returned by the generation endpoint. Saving is limited to 30 teams per hour per client.
Example:
curl -X POST https://crob.at/api/random-team/gen9ou/save \
-H "Content-Type: application/json" \
-d '{"teamText":"Gholdengo @ Leftovers\n..."}'
Response:
{
"slug": "abc123",
"url": "https://crob.at/abc123",
"image": "https://crob.at/og/abc123.png"
}
Returns 400 for an invalid format or team, 429 when rate limited, and 500 when the team cannot be saved.
GET /api/samples/:tier
Return Smogon sample teams for a given tier (format). Results are cached for one hour.
Example:
curl https://crob.at/api/samples/gen9ou
const response = await fetch('https://crob.at/api/samples/gen9ou');
const teams = await response.json();
console.log(teams[0].name);
import requests
response = requests.get('https://crob.at/api/samples/gen9ou')
teams = response.json()
print(teams[0]['name'])
Response:
[
{
"slug": "abc123",
"name": "Sun Offense",
"author": "Smogon",
"tier": "gen9ou",
"views": 142,
"created_at": "2026-01-15 10:00:00",
"url": "https://crob.at/abc123",
"image": "https://crob.at/og/abc123.png",
"source_url": "https://www.smogon.com/forums/..."
}
]
Returns 400 if :tier contains non-alphanumeric characters. Returns an empty array if no sample teams exist for that tier.
GET /api/type-chart-data
Return Pokémon names paired with their one or two types. Results are cached for 24 hours.
Example:
curl https://crob.at/api/type-chart-data
Response:
[
["Bulbasaur", ["Grass", "Poison"]],
["Charmander", ["Fire"]]
]
GET /api/me
Return the currently authenticated crob.at user, if any. This endpoint is intended for same-origin session-aware UI.
Example:
curl https://crob.at/api/me \
-H "Cookie: session=your-session-cookie"
const response = await fetch('https://crob.at/api/me', {
credentials: 'include'
});
const data = await response.json();
console.log(data.user);
import requests
session = requests.Session()
session.cookies.set('session', 'your-session-cookie')
response = session.get('https://crob.at/api/me')
data = response.json()
print(data['user'])
Response:
{
"user": {
"username": "exampleuser",
"email": "[email protected]",
"ps_username": "ExampleUser",
"team_count": 7,
"favorite_count": 3
}
}
Returns {"user": null} when no valid crob.at session is present.
POST /api/feedback
Send product feedback with optional contact details. Messages are limited to 2,000 characters and to five submissions per hour per client.
Example:
curl -X POST https://crob.at/api/feedback \
-H "Content-Type: application/json" \
-d '{"message":"The team viewer is useful.","contact":"[email protected]"}'
Response:
{"success": true}
Returns 400 for an empty or invalid message, 429 when rate limited, and 500 when feedback cannot be saved.
POST /api/showdown/assertion
Exchange the logged-in user's stored Showdown OAuth token for a challstr-specific assertion. Requires an authenticated crob.at session.
Request:
{
"challstr": "12345|abcdefghijklmnopqrstuvwxyz"
}
Example:
curl -X POST https://crob.at/api/showdown/assertion \
-H "Content-Type: application/json" \
-H "Cookie: session=your-session-cookie" \
-d '{"challstr":"12345|abcdefghijklmnopqrstuvwxyz"}'
const response = await fetch('https://crob.at/api/showdown/assertion', {
method: 'POST',
credentials: 'include',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
challstr: '12345|abcdefghijklmnopqrstuvwxyz'
})
});
const data = await response.json();
console.log(data.assertion);
import requests
session = requests.Session()
session.cookies.set('session', 'your-session-cookie')
response = session.post(
'https://crob.at/api/showdown/assertion',
json={'challstr': '12345|abcdefghijklmnopqrstuvwxyz'},
)
data = response.json()
print(data['assertion'])
Response:
{
"username": "exampleuser",
"assertion": "..."
}
The challenge string must be non-empty, at most 2,048 characters, and contain no line breaks. Returns 401 when a linked Showdown login is required and 503 when Showdown OAuth is unavailable.
Plans
All documented API operations are free. Public API endpoints do not require an account, API key, payment method, or paid plan. Optional account and Pokémon Showdown helpers use the normal crob.at browser session.
Rate limits
POST /api/team: 500 created teams per hour per clientPOST /api/random-team/:format/save: 30 saved random teams per hour per clientPOST /api/feedback: 5 messages per hour per client- Limited responses use HTTP
429and includeRetry-After. Write endpoints also expose applicableX-RateLimit-*headers.
API terms
Use the API lawfully and in a way that does not disrupt crob.at or other users. Do not bypass rate limits, probe for private data, submit abusive or infringing material, or falsely imply that crob.at, Pokémon Showdown, Nintendo, Game Freak, or The Pokémon Company endorses your integration. Public team content remains the responsibility of the person who submitted it. The API is provided as available without a service-level guarantee and may evolve; material contract changes will be reflected in the OpenAPI document and repository history. The privacy policy explains how crob.at handles account and request data.
Security
Public endpoints are anonymous and keyless. Session-aware endpoints use a secure crob.at browser session and act only on the signed-in account. Never send a crob.at session cookie to another origin or embed it in client code. To report a vulnerability privately, email [email protected] with reproduction details and avoid accessing or changing data that is not yours.
Notes
- Teams must be in Pokemon Showdown export format
POST /api/teamaccepts JSON only- Team text is limited to 500,000 characters per request
- Supports single and multi-team pastes
/api/team,/api/team/:slug,/api/team/:slug/:teamSlug,/api/random-team/:format,/api/random-team/:format/save,/api/samples/:tier,/api/type-chart-data, and/api/feedbackallow cross-origin requests/api/meand/api/showdown/assertionare session-based helper endpoints for crob.at UI/auth flows