crob.at

Find, build, analyze, and share teams

My teams

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

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