API Documentation
Everything returns JSON. CORS is on. Base URL:
The full specification is available as an OpenAPI document.
Authentication
No key needed to start. You get 30 requests per minute immediately. For higher limits, pass your Usage or Pro key in the X-API-Key header:
With a Usage or Pro key:
Rate Limits
Free requests are capped per IP. Usage and Pro keys are capped per key. Hit the limit and you'll get a 429 with a Retry-After header.
| Tier | Rate | Price | Scope |
|---|---|---|---|
| Free | 30 / min | $0 | Per IP |
| Usage | 120 / min | $2 / 1k requests | Per key |
| Pro | 300 / min | $25 / month | Per key |
Deleted content
When an artist removes their work, the API marks the post "deleted": true and returns a null source URL. Tags and metadata stay available. The /search endpoint excludes deleted posts; /post returns them by ID. Thumbnails are removed for deleted posts; the /thumb endpoint returns 404.
Endpoints
GET /api/v1/search
Find posts by natural language, text, tag, artist, or platform. Combine parameters to narrow results.
| Parameter | Type | Description |
|---|---|---|
| nl | string | Natural language query. The API extracts platform, artist, date range, tags, rating, sort order, and exclusions from your text. Leftover words become a full-text search. See Natural language search. |
| q | string | Full-text search query. Searches post titles, descriptions, and indexed content using PostgreSQL full-text search. Supports quoted phrases, OR, and -exclusion. |
| tag | string | Exact tag match (normalized form, e.g. genshin_impact). Use /tags to discover available tags. |
| artist | string | Artist handle or display name (case-insensitive pattern match). |
| platform | string | Platform slug: x, pixiv, bluesky, deviantart, artstation, newgrounds, tumblr, zerochan. |
| sfw | string | SFW filter, on by default. Only posts tagged rating:general or rating:sensitive are returned. Set to 0 to include all ratings. |
| rating | string | Filter to a specific rating tag: general, sensitive, questionable, or explicit. Takes precedence over sfw if both are set. |
| from | string | Start date filter (YYYY-MM-DD). Only return posts published on or after this date. |
| to | string | End date filter (YYYY-MM-DD). Only return posts published on or before this date. |
| since | string | Ingestion timestamp filter (ISO 8601). Only return posts added to the index on or after this time. Unlike from/to (which filter by publication date), since filters by when the post was indexed. Use this to monitor for newly indexed content. |
| limit | integer | Results per page, 1-100 (default: 50). |
| offset | integer | Pagination offset (default: 0). Use with limit to page through results. |
| color | string | Filter by dominant color. Accepts named colors (red, orange, yellow, green, cyan, teal, blue, purple, pink, brown, black, white, gray), moods (pastel, dark, warm, cool), or hex codes (#FF6600). Comma-separate for AND logic: color=blue,warm returns posts with both blue and warm tones in their palette. Maximum 4 colors per query. ~79% of posts have palette data. |
| sort | string | Sort order: newest (default) or oldest. When q is used without an explicit sort, results are ordered by relevance instead. |
| facets | string | Set to 1 to include faceted counts. Returns total result count, per-platform breakdown, and per-rating breakdown for the full unpaginated result set. |
At least one of nl, q, tag, artist, since, or color is required.
Choosing your search approach
The search endpoint offers three ways to query, depending on where the input comes from and how much control you need.
nl: Natural language. Use when the input comes from a human typing freeform text. The API parses platform, artist, dates, tags, rating, sort, and exclusions out of the text. Multi-word phrases that match known tags (like "white hair" or "dark skin") are resolved to exact tag filters. Anything left becomes a full-text search. Best for: search bars, chatbot commands, user-facing input where you don't want to build your own parser.
q: Full-text search. Use when you want direct control over what gets searched against post titles, descriptions, and indexed content. No parsing, no extraction. The string goes straight to the search engine. Supports PostgreSQL websearch_to_tsquery syntax: quoted phrases for exact match, OR for alternatives, -word to exclude. Best for: programmatic queries where you've already parsed the input yourself, or when you want to combine structured params with a specific text search.
Explicit parameters: tag, artist, platform, from, to, sfw, rating. Use when you have structured UI controls (dropdowns, date pickers, checkboxes, tag selectors) and know exactly what each filter is. Best for: filter panels, faceted search, data pipelines, automated queries.
Combining them. All three compose. Explicit parameters override anything the NL parser extracts for the same field. This lets you have a search bar (nl) alongside structured controls (checkboxes, dropdowns) that always take priority.
Content rating tags
Every post carries a rating tag that classifies the source artwork. The API returns this as a text tag in the JSON response alongside all other tags. Lagoon serves structured metadata and scaled thumbnails for identification purposes. Full-resolution artwork is not hosted or transmitted.
| Rating tag | Source artwork classification |
|---|---|
| general | No suggestive elements in the source artwork. |
| sensitive | Mildly suggestive source artwork (e.g. swimwear). |
| questionable | Suggestive source artwork. |
| explicit | Adult source artwork. |
SFW filtering is on by default. All queries return only general and sensitive content unless you opt out:
Use-case examples
Gallery app with tag browsing. Your app shows a grid of art filtered by tag with pagination. SFW filtering is on by default.
Discord bot search command. Users type /search touhou on twitter newest. Pass their input directly to nl. The parsed filters in the response tell you what was extracted, which you can show back to the user.
Monitoring an artist for new posts. Check for new work by a specific artist on a specific platform since a given date. Run this on a schedule to alert when new posts appear.
Index monitoring with since. Track newly indexed content by ingestion time. Unlike from/to (which filter by publication date), since filters by when the post was added to the index. Use this to build feeds or detect new content without fragile offset tracking.
Search bar with structured controls. Your UI has a text input, a platform dropdown, and an SFW checkbox. The text input feeds nl, the dropdown feeds platform, and the checkbox feeds sfw. Explicit params override anything the NL parser finds.
Date-bounded data export. Pull all posts matching a tag within a date range. Page through the full result set.
Browsing an artist's full portfolio. First look up the artist to see all their linked accounts, then search for their posts.
Faceted search for filter panels. Pass facets=1 to get the total result count and breakdowns by platform and rating. Use this to build filter UIs with accurate counts.
Color palette search. Filter posts by dominant colors in their extracted palette. Combine with other filters to find art with specific color schemes.
Full-text search with OR and exclusion. Use q directly when you need PostgreSQL search syntax that the NL parser doesn't handle.
Parameter precedence
When nl is combined with explicit parameters, the explicit parameter always wins for the same field:
| NL extracts | Explicit param | Result |
|---|---|---|
| platform=x | platform=pixiv | platform=pixiv |
| from=2026-05-09 | from=2025-01-01 | from=2025-01-01 |
| q=armor | q=sword | q=sword |
| rating=general | (none) | rating=general (specific rating overrides default SFW) |
| (none) | sfw=0 | SFW filter off (all ratings returned) |
Pagination
Use limit and offset to page through results. The response count tells you how many results were returned in this page. To get the next page, add the current limit to the current offset. When count is less than limit, you've reached the last page.
Example response:
width and height are the original image dimensions in pixels (null when not available from the source platform; ~99.8% coverage). palette is an array of up to 6 dominant hex colors extracted from the image, ordered roughly by dominance (null when not available; ~79% coverage).
When facets=1 is passed, the response includes a facets object with the total unpaginated count and breakdowns by platform and content rating:
Natural language search
The nl parameter accepts plain English queries. The API parses your text into structured filters, runs the search, and returns both the results and the filters it extracted so you can see exactly how the query was interpreted.
The parser recognizes:
| Category | Examples | What it becomes |
|---|---|---|
| Platforms | on twitter, from pixiv, bluesky | platform=x |
| Artists | @username, by sakimichan | handle=sakimichan |
| Dates | last 3 months, from 2024, march 2025, summer 2024, yesterday, this week | from=... and to=... |
| Tags | dark skin, blue hair, species:cat, solo, two girls | tags=[...] |
| Rating | sfw, nsfw, explicit, wholesome | rating=... |
| Sorting | newest, oldest, earliest | sort=oldest |
| Exclusions | not from pixiv, without watermark, minus @user | NOT filters on platform, handle, or text |
| Limits | top 10, first 5 | limit=10 |
Filler words like "show me", "find", "art", "posts", "please" are stripped automatically. Multi-word phrases left after parsing are matched against known tags and converted to exact tag filters when found (e.g. "dark skin" resolves to the dark_skin tag). Any remaining text becomes the full-text search query (q).
When nl is used, the response includes a filters key showing what was extracted:
More NL examples:
GET /api/v1/random
Return random posts from the index. Accepts the same filters as /search (tag, platform, artist, date range, rating, NL) to narrow the pool before sampling. No required parameters.
| Parameter | Type | Description |
|---|---|---|
| nl | string | Natural language filter. Same parser as /search: extracts platform, date, tags, rating, exclusions. |
| q | string | Full-text search filter. Only posts matching this text are eligible for random selection. |
| tag | string | Exact tag match (normalized). |
| artist | string | Artist handle or display name (case-insensitive). |
| platform | string | Platform slug. |
| sfw | string | SFW filter, on by default. Set to 0 to include all ratings. |
| rating | string | Filter to a specific rating. |
| from | string | Start date (YYYY-MM-DD). |
| to | string | End date (YYYY-MM-DD). |
| limit | integer | Number of random posts to return, 1-50 (default: 1). |
Example requests:
Response format is identical to /search, with each result including tags and description_long. When nl is used, the response includes a filters key showing what was extracted.
GET /api/v1/artist
Query a handle and get back every linked account across platforms. Use this to build profile pages, cross-reference artists, or verify that two handles belong to the same person.
| Parameter | Type | Description |
|---|---|---|
| handle required | string | Artist handle to look up. When platform is set, must be the platform-specific handle (e.g. a Pixiv numeric user ID, not the artist's X handle). |
| platform | string | Narrow the handle match to a specific platform. Useful when the same handle string exists on multiple platforms for different people. |
When to use this vs. /search?artist=: This endpoint returns the artist's profile (linked accounts, post count). The artist param on /search returns the artist's posts. Use this first to confirm you have the right artist, then search their posts.
Example request:
Example response:
Use-case: disambiguating a Pixiv artist by their numeric ID.
GET /api/v1/post
Get full metadata for a single post by ID: tags, extended description, artist profile, and source link. Use this when you already have a post ID from a search result and want the complete data (search results include core fields; this endpoint adds description_long and artist_profile).
| Parameter | Type | Description |
|---|---|---|
| id required | integer | Post ID (from search results or your own database). |
Example request:
Example response:
GET /api/v1/tags
Search the tag index by name. Use this to discover available tags, build tag autocomplete, or check tag post counts before searching. Returns matching tags sorted by category priority (character, copyright, then general) and by post count within each category.
| Parameter | Type | Description |
|---|---|---|
| q required | string | Tag name search (min 2 characters). Matches anywhere in the tag name. |
| limit | integer | Max results, 1-50 (default: 20). |
Tags are categorized as character, copyright, species, rating, or general. Use this to find the exact normalized tag name before passing it to /search?tag=.
Example: building a tag autocomplete. User types "gen" into your search field, you query the tag index to show suggestions.
Example response:
Then use the tag name in a search:
GET /api/v1/stats
Returns index-wide statistics: total posts, artists, tags, and a per-platform breakdown of searchable post counts. No parameters required.
Use this to check data coverage before integrating, display index size in your UI, or verify which platforms are tracked.
Example request:
Example response:
| Field | Type | Description |
|---|---|---|
| total_posts | integer | Total indexed posts across all platforms. |
| total_artists | integer | Total unique artists tracked across all platforms. |
| total_tags | integer | Total tags with at least 3 posts (the same threshold used by /tags). |
| platforms | array | Per-platform breakdown, sorted by post count descending. Each entry has slug, name, and post_count. Post counts reflect the searchable subset: tagged, non-duplicate, non-deleted posts matching what /search returns. |
GET /api/v1/co-occurrence Analytics
Tag co-occurrence data. Returns tags that frequently appear alongside a given tag, with counts and Jaccard similarity scores. Requires an API key (not available on the free tier). Billed at the analytics rate: 10 mills per call ($10/1,000 requests).
| Parameter | Type | Description |
|---|---|---|
| tag | string | Required. Tag name to look up co-occurrences for. Matched by highest post count, so genshin_impact resolves to copyright:genshin impact. |
| limit | integer | Max results. Default 20, max 100. |
| min_count | integer | Minimum co-occurrence count to include. Default 10. |
Example request:
Example response:
| Field | Type | Description |
|---|---|---|
| tag | string | The resolved tag name. |
| tag_post_count | integer | Number of active posts with this tag. |
| co_occurrences[].tag | string | Co-occurring tag name. |
| co_occurrences[].category | string | Tag category: general, character, copyright, species, or rating. |
| co_occurrences[].count | integer | Number of posts where both tags appear together. |
| co_occurrences[].jaccard | number | Jaccard similarity (intersection / union). Ranges from 0 to 1; higher means stronger association. |
GET /api/v1/trending Analytics
Tags that are appearing disproportionately often in recently indexed posts compared to their historical rate. The trend score is the ratio of observed frequency to expected frequency: a score of 10 means the tag appeared 10x more than its baseline rate in the window. Requires an API key (not available on the free tier). Billed at the analytics rate: 10 mills per call ($10/1,000 requests).
| Parameter | Type | Description |
|---|---|---|
| window | string | Time window to measure. One of 1h, 6h, 12h, 24h, 48h, 7d. Default 24h. |
| limit | integer | Max results. Default 20, max 50. |
| min_posts | integer | Minimum total post count for a tag to be eligible. Default 10. Increase to filter out rare tags. |
| category | string | Filter to a specific tag category: character, copyright, species, rating, or general. |
Example request:
Example response:
| Field | Type | Description |
|---|---|---|
| window | string | The time window used. |
| tags[].tag | string | Tag display name. |
| tags[].normalized | string | Normalized tag name (pass to /search?tag=). |
| tags[].category | string | Tag category: general, character, copyright, species, or rating. |
| tags[].recent_count | integer | Number of posts with this tag in the window. |
| tags[].total_count | integer | Total posts with this tag across the entire index. |
| tags[].trend_score | number | Observed/expected ratio. Higher means stronger trend. A score of 1.0 means the tag is appearing at its normal rate. |
Filter to specific tag categories to focus on what matters:
GET /api/v1/batch-similar Compute
Pairwise cosine similarity matrix for a set of post IDs. Returns all pairs above a similarity threshold. Requires an API key (not available on the free tier). Billed per input post ID: 10 mills each. N=50 posts costs 500 mills ($0.50).
| Parameter | Type | Description |
|---|---|---|
| ids | string | Required. Comma-separated post IDs. Maximum 100 on Usage tier, 200 on Pro. |
| threshold | number | Minimum similarity score to include in results. Default 0.5. Range 0 to 1. |
Response
| Field | Type | Description |
|---|---|---|
| pairs[].id_a | integer | First post ID (always less than id_b). |
| pairs[].id_b | integer | Second post ID. |
| pairs[].similarity | number | Cosine similarity (0 to 1). Higher means more similar. |
| missing_ids | array | Post IDs from the request that had no embedding. Omitted if all IDs were found. |
Pairs are sorted by similarity descending. Duplicate IDs in the input are deduplicated. Each unique pair appears once (id_a < id_b).
GET /api/v1/similar
Find semantically similar posts using vector embeddings. Two modes: pass a post ID to find posts similar to it, or pass a text query to find posts matching a description. All filters from /search (platform, artist, SFW, rating) compose with similarity.
| Parameter | Type | Description |
|---|---|---|
| id | integer | Post ID to find similar posts for. Mutually exclusive with q. |
| q | string | Text description to match semantically (3-500 characters). Mutually exclusive with id. |
| platform | string | Filter results to a specific platform slug. |
| artist | string | Filter results to a specific artist handle (case-insensitive). |
| sfw | string | SFW filter, on by default. Set to 0 to include all ratings. |
| rating | string | Filter to a specific rating. Overrides sfw. |
| limit | integer | Max results, 1-50 (default: 10). |
At least one of id or q is required.
How it works
Every post has a vector embedding derived from its tags and metadata. The endpoint ranks results by cosine similarity (0 to 1, where 1 is identical), then applies any filters you specify.
When using q, your text is encoded at request time. Describe what you're looking for in plain language and get results ranked by semantic relevance, not keyword overlap.
Similar by ID
Find posts similar to one you already have:
Semantic text search
Describe what you're looking for. Unlike /search?q= (which matches keywords in titles and descriptions), this matches against the semantic content of posts:
When to use /similar vs. /search
| Use case | Endpoint | Why |
|---|---|---|
| User typed "blue hair sword on pixiv" | /search?nl=... | NL parser extracts structured filters from casual text. |
| Find posts like one you already have | /similar?id=... | Vector similarity finds visually/thematically related posts. |
| Match a description no tag covers | /similar?q=... | Semantic matching finds posts even without exact tag or keyword overlap. |
| Autocomplete or tag browsing | /tags?q=... | Tag index search, not semantic. |
Example response:
The similarity field ranges from 0 to 1. Values above 0.8 indicate strong similarity; values above 0.9 typically mean the posts share most of the same tags and subject matter.
GET /api/v1/similar-artists Analytics
Find artists with a similar visual style. Each artist in the index has a pre-computed style vector derived from their body of work. Pass an artist handle to find stylistically similar artists, or a post ID to find artists whose overall style matches that post. Requires an API key (not available on the free tier). Billed at the analytics rate: 10 mills per call ($10/1,000 requests).
| Parameter | Type | Description |
|---|---|---|
| handle | string | Artist handle to find similar artists for. Mutually exclusive with id. |
| platform | string | Platform slug to disambiguate the handle (e.g. x, pixiv). Optional; without it, the handle with the most indexed posts is used. |
| id | integer | Post ID. Finds artists whose style matches this specific post. Mutually exclusive with handle. |
| limit | integer | Max results. Default 10, max 50. |
Similar artists by handle
Similar artists by post
Response
| Field | Type | Description |
|---|---|---|
| results[].handle | string | Primary handle for this artist (the one with the most indexed posts). |
| results[].platform | string | Platform slug for the primary handle. |
| results[].post_count | integer | Number of indexed posts for this handle. |
| results[].similarity | number | Cosine similarity of style vectors (0 to 1). Higher means more stylistically similar. |
| results[].also_on | array | Other known handles for the same artist on other platforms. Omitted if none are linked. |
When searching by handle, the source artist and all their linked handles are excluded from results. When searching by post ID, no artist is excluded.
GET /api/v1/thumb
Returns a scaled thumbnail image for a post. The response is a WebP image (max 360px on the longest side), not JSON. Errors are still returned as JSON.
Thumbnail requests do not require an API key and do not count toward API rate limits or usage billing. They have a separate IP-based limit of 300 per minute.
| Parameter | Type | Description |
|---|---|---|
| id | integer | Post ID (required). |
Every post object returned by /search, /post, /similar, and /random includes a thumb_url field pointing to this endpoint.
Responses include Cache-Control: public, max-age=86400 and Last-Modified headers. Conditional requests with If-Modified-Since return 304 when the thumbnail has not changed.
Returns 404 for posts that do not exist, have been removed at source, or have no cached thumbnail.
Example:
Errors
Every error returns "ok": false with an "error" message. Standard HTTP codes:
400- Missing or invalid parameters401- Bad or expired API key402- Insufficient balance (Usage tier, balance is zero)404- Not found429- Rate limit hit (checkRetry-Afterheader)500- Server error
Example error response:
CORS
Every endpoint allows * origins and responds to OPTIONS preflight, so you can call from the browser without a proxy.