Crossword API

Search words, find clues, fill grids, and publish puzzles.
On this page

The Crossword API enables you to build crosswords into your own site, app, or product. You can search words, find clues, fill grids, and publish playable puzzles in 24 languages.

The API sends and receives JSON. Autofill can also send live progress while it works. Get a free key with an account, then start with curl, the xword client and CLI, or the MCP server.

Rules for Use

The Crossword API lets you build, submit, and showcase your puzzles programmatically. You must follow the Terms of Use, and we may turn off API keys for abuse.

  1. Credit. If your site or app uses this API, show a clear link to crossword.texs.org and use one of the linked logos below. Printed puzzles must show the logo or say “Powered by Crossword Generator” with the URL.
  2. No impersonation. The author can be your name, a pen name, your site’s name, or the name of someone who agreed to be credited. Do not use the name of a person, company, or publication that did not make the puzzle.
  3. Content. The site’s rules apply to every part of a puzzle. Do not publish anything illegal, hateful, or harassing. Our safety check may miss things, so you are still responsible for what you publish.
  4. No harvesting. Word search returns at most 100 results. Do not use many requests to copy the full word lists or clue collection. See the data licenses for rules about the data.
html
<a href="https://crossword.texs.org" aria-label="Crossword Generator">
  <picture>
    <source srcset="https://crossword.texs.org/crossword-logo.avif" type="image/avif">
    <source srcset="https://crossword.texs.org/crossword-logo.webp" type="image/webp">
    <img src="https://crossword.texs.org/crossword-logo.gif"
         alt="Powered by texs.org — Crossword Generator" width="500" height="120"
         style="max-width:100%;height:auto">
  </picture>
</a>

Pricing

API access is included with your account. There is no extra API bill. Your key uses the same limits as the web app.

TierPriceReads / minuteFills / monthNew puzzles / monthMax gridAI clues / month
FreeFree30303013×130
Starter$6 / month or $60 / year6020020023×23250
Partner$12 / month or $120 / year12060060023×231,200
Patron$25 / month or $250 / year3002,0002,00023×235,000

A clean-up counts as one fill. Creating a puzzle uses your monthly puzzle limit. Editing or publishing it does not. Repeating a create request with the same Idempotency-Key also does not count again.

Need more than the Patron plan? Email tex@texs.org with how many requests you expect, your grid size and language, and whether you need AI clues.

Quickstart

This quickstart does three things: gets a key, searches for words, and fills a grid. You can view each request as curl, JavaScript, or an xword command.

1. Create a key

Sign in and open the API tab of your dashboard. Copy the key right away. You will only see it once.

shell
export CROSSWORD_API_KEY=cw_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

2. Search the word index

Use _ for an unknown letter. Scores run from 0 to 100. Words with a score of 40 or more are usually good for a clean fill.

request
curl -s 'https://crossword.texs.org/api/v1/words?pattern=C_T&lang=en&min_score=40' \
  -H "Authorization: Bearer $CROSSWORD_API_KEY"
response
{
  "words": [
    { "word": "COT", "score": 90 },
    { "word": "CUT", "score": 90 },
    { "word": "CAT", "score": 80 },
    { "word": "CDT", "score": 50 },
    { "word": "CST", "score": 50 },
    { "word": "CIT", "score": 40 },
    { "word": "CRT", "score": 40 }
  ]
}

3. Fill a grid

A grid is a list of text rows. Use . for an empty cell and# for a black cell. The solver fills empty cells and keeps any letters you already added.

request
curl -s https://crossword.texs.org/api/v1/fill \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "grid": ["..#..", ".....", "..A..", ".....", "..#.."],
    "language": "en",
    "min_score": 40
  }'
response (one run)
{
  "grid": ["KD#MC", "NINER", "OVATE", "LEGOS", "LR#OT"],
  "slotsFilled": 8,
  "slotsTotal": 8,
  "sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T",
  "quality": { "scored": 8, "rough": [] }
}
what you sent
what came back

The solver may give you a different answer each time. In this language, a word must be at least three cells long, so the solver skips two-cell spaces.

Example: build and publish a puzzle

This example uses the theme words from Greek Mythology by Jerrie Stack. It keeps the black squares and five theme entries (ATLAS, ZEUS, PAN, MEDUSA, APOLLO). The API fills the rest. Every response below came from a real request.

1. Check the theme candidates

First, check which theme words are in the word list. A missing word is left out of the response. It is not given a score of 0. You can still place and lock a missing word in the grid.

request
curl -s https://crossword.texs.org/api/v1/words/scores \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"words": ["ZEUS", "HADES", "PERSEPHONE", "XQZZY"], "language": "en"}'
response
{
  "scores": { "ZEUS": 80, "HADES": 80, "PERSEPHONE": 90 }
}

2. Fill around the theme

Letters already in the grid stay in place. min_score: 40asks for good common words. max_time: 25 stops the search after 25 seconds.

request
curl -s https://crossword.texs.org/api/v1/fill \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "language": "en",
    "min_score": 40,
    "max_time": 25,
    "grid": [
      "##M..###Z..",
      "#.E..###E..",
      "..D....#U..",
      "..U##...S##",
      "..S##PAN###",
      "#.A.##..A..",
      "###....#P..",
      "#ATLAS##O..",
      "...#....L..",
      "...##...L##",
      "...##...O##"
    ]
  }'
response (one run)
{
  "grid": [
    "##MMI###ZAP",
    "#HEAR###EYE",
    "CODDERS#USC",
    "IOU##APES##",
    "ODS##PAN###",
    "#SAC##CLAMP",
    "###PACE#POE",
    "#ATLAS##ORS",
    "AME#STARLET",
    "DMS##ARAL##",
    "DOT##RENO##"
  ],
  "slotsFilled": 44,
  "slotsTotal": 44,
  "sessionId": "p8Vq2mJx0Rt5Kc7Yw1Ln3Hs9",
  "quality": { "scored": 39, "rough": [] }
}
the skeleton (theme letters tinted)
one fill, 44 of 44 slots

3. Clean up, if anything is rough

Check quality.rough after a fill. It lists weak words the solver used to finish the grid. If the list is not empty, callPOST /fill/improve. Put theme cells in locked so the clean-up does not change them.

request
curl -s https://crossword.texs.org/api/v1/fill/improve \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "language": "en",
    "grid": [ ...the filled grid... ],
    "locked": ["0,2", "1,2", "2,2", "3,2", ...every theme cell... ]
  }'

4. Clue every answer in one call

Send up to 500 answers to POST /clues/bulk. It returns up to five clues for each answer, with the best clue first. Members can use POST /clues/generate to write new clues.

request
curl -s https://crossword.texs.org/api/v1/clues/bulk \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"words": ["ATLAS", "MEDUSA"], "language": "en"}'
response (abridged)
{
  "clues": {
    "ATLAS": [
      { "text": "Geography reference volume", "source": "original", "pubCount": 0 },
      { "text": "Book of map charts", "source": "original", "pubCount": 0 },
      { "text": "World music compilation, loosely", "source": "original", "pubCount": 0 },
      { "text": "Titan bearing heavens in myth", "source": "original", "pubCount": 0 },
      ...
    ],
    "MEDUSA": [
      { "text": "Gorgon slain by Perseus", "source": "original", "pubCount": 0 },
      { "text": "Snake-haired mythological figure", "source": "original", "pubCount": 0 },
      { "text": "One of three Gorgons", "source": "original", "pubCount": 0 },
      ...
    ]
  }
}

5. Save it

Group clues into across and down, then use each clue number as a key. Do not send size; the API gets it from the grid. The response includes the saved puzzle and its ID.

request
curl -s https://crossword.texs.org/api/v1/puzzles \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Greek Mythology",
    "author": "Jerrie Stack",
    "language": "en",
    "grid": [ ...the filled grid... ],
    "clues": {
      "across": { "1": "Poli ___", "4": "Driving hazard" },
      "down":   { "1": "Southwest Japanese port", "2": "Young horse" }
    },
    "themeWords": ["ATLAS", "ZEUS", "PAN", "MEDUSA", "APOLLO"]
  }'

6. Publish

Publishing creates a public page and an embed URL. The puzzle stays unlisted unless you send submitToShowcase: true.

request
curl -s -X POST https://crossword.texs.org/api/v1/puzzles/6qix6k1x/publish \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"submitToShowcase": true}'
response (abridged)
{
  "id": "6qix6k1x",
  "status": "published",
  "showcaseStatus": "pending",
  "url": "https://crossword.texs.org/puzzle/6qix6k1x",
  "embedUrl": "https://crossword.texs.org/embed/6qix6k1x",
  ...
}

7. Export

Use format=puz for crossword apps. Use format=json for JSON data. .puz only works for languages that use the Latin alphabet.

request
curl -s 'https://crossword.texs.org/api/v1/puzzles/6qix6k1x/export?format=puz' \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" -o greek-mythology.puz

8. Embed it anywhere

Put embedUrlin an iframe. The optional script adjusts the iframe height, matches the puzzle's light or dark appearance to the page around it (add ?scheme=light or ?scheme=darkto the src to pin it instead), and tells your page when a player makes progress or solves the puzzle. The caption is ordinary HTML that inherits your page's font and colour, so it matches whatever theme surrounds it. Here is the live puzzle:

html
<iframe src="https://crossword.texs.org/embed/6qix6k1x"
        title="Greek Mythology" width="100%" height="1260"
        style="border:0;max-width:100%;display:block" loading="lazy"></iframe>
<script src="https://crossword.texs.org/embed.js" async></script>
<p style="display:flex;gap:1em;margin:0.5em 0 0;font-size:0.8em;opacity:0.75">
  <span>Made with <a href="https://crossword.texs.org/" style="color:inherit;text-decoration:none;font-weight:600">Crossword Generator</a></span>
  <a href="https://crossword.texs.org/puzzle/6qix6k1x" style="color:inherit;text-decoration:none;margin-left:auto">Open full puzzle ↗</a>
</p>

Authentication

Send your key in the Authorization header. The Metaendpoints and GET /openapi.json work without a key.

preferred
Authorization: Bearer cw_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

If you cannot set the Authorization header, useX-Api-Key: cw_live_… instead. Send only one of these headers.

Do not put a key in public browser code. Send requests through your own server so visitors cannot copy it.

Scopes

A scope controls what a key can do. New keys get all three scopes. Remove any scope your app does not need. If a key is missing a needed scope, the API returns 403 FORBIDDEN_SCOPE.

ScopeCovers
readSearch and score words, find clues, and read or export puzzles.
solveFill and clean grids, cancel a fill, and create AI clues.
puzzles:writeCreate, update, delete, and publish puzzles.

How to send a grid

Send a grid as a list of text rows, starting with the top row. Use. for an empty cell, # for a black cell, and a letter for a fixed cell. A grid must be square and 3 to 23 cells wide.

Put theme cells in locked so clean-up will not change them. Write each cell as "row,col". Counting starts at 0.

a 5×5 with one locked theme letter
"grid": [
  "..#..",
  ".....",
  "..A..",
  ".....",
  "..#.."
],
"locked": ["2,2"]
row 2, col 2 locked

In some languages, one visible letter uses more than one Unicode character. In JavaScript, count cells with Intl.Segmenter, not .length. The API may also clean up letter forms, so the returned text may not exactly match the bytes you sent.

Arabic and Hebrew use the same row and column order as other grids. Column 0 is still first. Only the screen display should be mirrored.

Rate limits and quotas

See the pricing table for each plan’s limits. Read requests have a per-minute limit. Fills and clean-ups use a monthly limit. Only a few fills can run at the same time.

Headers to watch

HeaderOnMeaning
X-RateLimit-LimitreadsRequests allowed per minute for this key.
X-RateLimit-RemainingreadsRequests left in the current minute.
Retry-After429sSeconds to wait before trying again.
X-Fill-Quota-RemainingsolverFills and clean-ups left this calendar month. -1 is unlimited.
X-Ai-Clues-Remainingclue generationAI clues left this calendar month. -1 is unlimited.
X-Fill-Session-IdsolverThe fill ID. Send it to POST /fill/cancel to stop a fill.

A word search returns at most 100 results. You cannot export the full word or clue database. See the data licenses.

Streaming a fill

By default, POST /fill waits and returns one JSON response. Send Accept: text/event-stream to get updates while it works.

request
curl -N https://crossword.texs.org/api/v1/fill \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Accept: text/event-stream' \
  -H 'Content-Type: application/json' \
  -d '{"grid": ["..#..", ".....", "..A..", ".....", "..#.."], "language": "en"}'
stream (abridged)
data: {"type":"session","sessionId":"kKq2x9Wn0ZqQm5Yc1Lr7Hs3T"}

data: {"type":"progress","filled":3,"total":8,"fill":{"1,0":"N","1,1":"I","1,2":"N"}}

data: {"type":"progress","filled":6,"total":8,"fill":{"1,0":"N","2,0":"O","3,0":"L"}}

data: {"type":"complete","grid":["KD#MC","NINER","OVATE","LEGOS","LR#OT"],
       "slotsFilled":8,"slotsTotal":8,"quality":{"scored":8,"rough":[]}}

The first event is session. Next come zero or moreprogress events. The last event is complete orerror. Use the session ID to cancel the fill:

cancel
curl -s https://crossword.texs.org/api/v1/fill/cancel \
  -H "Authorization: Bearer $CROSSWORD_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T"}'

A later update may show fewer filled cells. The solver sometimes backs up and tries again. Always replace the old grid with the newest one.

An error has one of these reason codes: too_difficult,no_solution, cancelled, or internal.

Errors

Error responses use application/problem+json. Use thecode field in your program. The detail text is for people and may change.

application/problem+json
{
  "type": "https://crossword.texs.org/developers/errors#GRID_SIZE_LOCKED",
  "title": "Grid size locked",
  "status": 403,
  "code": "GRID_SIZE_LOCKED",
  "detail": "15×15 grids require a membership",
  "maxGridSize": 13
}

Command line, client and MCP

The xword package gives you a TypeScript client, a command line tool, and an MCP server. It creates patterns and .puzfiles on your computer, so those actions do not use API limits. It is open source under MIT: github.com/texjer/xword.

install
npx xword status      # no install
npm i -g xword        # then: xword status
xword login           # prompts for the key, stores it mode 0600

Set CROSSWORD_API_KEY, or run xword login to save the key on your computer. You cannot pass a key as a command option, because it could leak into shell history. xword status andxword languages work without a key.

a tour
xword languages --available                 # the 24 languages and their constraints
xword words "C_T" --lang en --min-score 40  # pattern search; _ is a wildcard
xword clues SUB --limit 3                   # corpus clues for an answer

xword pattern --size 15 --out grid.txt      # local, no key, no quota
xword fill grid.txt --stream --out filled.txt
xword improve filled.txt --out clean.txt    # swap the obscure entries out

xword puzzles create puzzle.json            # save it
xword puzzles publish k3n8q1zp              # mint the public + embed URLs
xword export k3n8q1zp --puz --out mine.puz  # Across Lite

xword pattern --size 11 | xword fill - --stream --json   # everything pipes

Use --json when another program will read the result. Use--base to call another server. Pass - to read a grid from standard input.

MCP

xword mcp starts a Model Context Protocol server. This lets supported AI tools build and publish puzzles. Add it to Claude Code with this command:

claude code
claude mcp add crossword -e CROSSWORD_API_KEY=cw_live_… -- npx -y xword mcp

The same settings work in Claude Desktop, Cursor, and a project.mcp.json file. If you already ran xword login, you can leave out the env block.

mcp.json
{
  "mcpServers": {
    "crossword": {
      "command": "npx",
      "args": ["-y", "xword", "mcp"],
      "env": { "CROSSWORD_API_KEY": "cw_live_…" }
    }
  }
}

Then ask for a puzzle: “make me an 11×11 about lighthouses and give me the embed code.”

Docs for AI tools

AI tools and other programs can read these docs as plain Markdown. Request /developers with Accept: text/markdown, or use/developers.md. Error docs are at/developers/errors.md. /llms.txt lists all files.

fetching the docs as a program
curl -s https://crossword.texs.org/developers -H 'Accept: text/markdown'
curl -s https://crossword.texs.org/developers.md
curl -s https://crossword.texs.org/api/v1/openapi.json
curl -s https://crossword.texs.org/llms.txt

Get the OpenAPI file at GET /api/v1/openapi.json. It does not need a key. The Markdown version includes every request and response example. To let an AI tool call the API, install the MCP server.

Response fields use camelCase. Solver request fields use snake_case, such as min_score and max_time.

Endpoint reference

This section comes from the same OpenAPI file used by the server. It shows the fields and rules the API checks.

EndpointScopeWhat it does
get/statusno keyService status
get/languagesno keyList puzzle languages
get/wordsreadSearch words by pattern
post/words/scoresreadScore a batch of words
get/clues/{word}readClues for one answer
post/clues/bulkreadClues for many answers
post/clues/generatesolveGenerate AI clues for an answer
post/fillsolveAuto-fill a grid
post/fill/improvesolveClean up a filled grid
post/fill/cancelsolveCancel a running fill
get/puzzlesreadList your puzzles
post/puzzlespuzzles:writeCreate a puzzle
get/puzzles/{id}readGet one puzzle
patch/puzzles/{id}puzzles:writeUpdate a puzzle
delete/puzzles/{id}puzzles:writeDelete a puzzle
post/puzzles/{id}/publishpuzzles:writePublish a puzzle
get/puzzles/{id}/exportreadExport a puzzle

Meta

Check the service and list languages. No key is needed.

get/statusno key

Service status

Check whether the API and each language index are ready. No key is needed, but requests are limited by IP address.

This endpoint returns 200 even when the solver is down. In that case, status is "degraded" and languages is empty. Watch the status field, not only the HTTP status. The only other response is 429.

Response

200 `ok` when the solver is healthy. `degraded` when it is not.

application/json

PropertyTypeNotes
statusrequiredstring

one of "ok", "degraded"

versionrequiredstring

API contract version.

languagesrequiredobject<string, object>

Per-language index state, keyed by language code.

Healthy, English resident
{
  "status": "ok",
  "version": "1.0.0",
  "languages": {
    "en": {
      "available": true,
      "loaded": true
    },
    "ru": {
      "available": true,
      "loaded": false
    },
    "hi": {
      "available": true,
      "loaded": false
    }
  }
}
The solver could not be reached
{
  "status": "degraded",
  "version": "1.0.0",
  "languages": {}
}

Failures

  • 429

    Per-minute rate limit exceeded for this key's tier.

get/languagesno key

List puzzle languages

List every supported language and its main rules. The response shows whether the language is ready and whether it needs a criss-cross grid. It also shows reading direction, .puz support, and the shortest answer.

pt-BR is accepted as input but is returned as pt. Both forms of Portuguese use the same data. available: false means word search and fill will not work for that language.

No key is needed, but requests are limited by IP address.

Response

200 The language registry.

application/json

PropertyTypeNotes
languagesrequiredarray of object
coderequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

namerequiredstring

English name.

nativeNamerequiredstring

The language's own name.

availablerequiredboolean

true when word search and fill work for this language.

crissCrossOnlyrequiredboolean

true for Chinese, Japanese, and Korean. Build these as criss-cross grids. POST /fill cannot fill a dense grid for them.

rtlrequiredboolean

true for a right-to-left language. The data still starts at column 0. Mirror only the display.

puzExportablerequiredboolean

true when this language can be saved as a .puz file.

minSlotLengthrequiredinteger

The shortest space that counts as an answer. Usually 3 cells, or 2 for Chinese, Japanese, and Korean.

Three representative entries
{
  "languages": [
    {
      "code": "en",
      "name": "English",
      "nativeName": "English",
      "available": true,
      "crissCrossOnly": false,
      "rtl": false,
      "puzExportable": true,
      "minSlotLength": 3
    },
    {
      "code": "he",
      "name": "Hebrew",
      "nativeName": "עברית",
      "available": true,
      "crissCrossOnly": false,
      "rtl": true,
      "puzExportable": false,
      "minSlotLength": 3
    },
    {
      "code": "ja",
      "name": "Japanese",
      "nativeName": "日本語",
      "available": true,
      "crissCrossOnly": true,
      "rtl": false,
      "puzExportable": false,
      "minSlotLength": 2
    }
  ]
}

Failures

  • 429

    Per-minute rate limit exceeded for this key's tier.

Words

Search and score words in 24 languages.

get/wordsread

Search words by pattern

Search a language's word list. Use _ or ? for an unknown letter. Results are sorted by score, highest first.

Scores run from 0 to 100. A score of 40 or more is usually good for a clean fill. A search returns at most 100 words. You cannot use this endpoint to download the full word list.

Parameters

NameInTypeNotes
patternrequiredquerystring

The slot pattern. _ or ? for an unknown cell.

max 23 characters · e.g. C_T

langquerystring

Puzzle language code.

default "en" · one of 25 values · e.g. en

min_scorequeryinteger

Drop entries scoring below this.

default 0 · 0–1000 · e.g. 40

limitqueryinteger

Maximum matches to return.

default 50 · 1–100

Response

200 Matching words, best first.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
wordsrequiredarray of object
wordrequiredstring

The entry, normalized for its language.

scorerequiredinteger

Word quality from 0 to 100. A score of 40 or more is usually good for a clean fill.

GET /words?pattern=C_T&lang=en&min_score=40
{
  "words": [
    {
      "word": "COT",
      "score": 90
    },
    {
      "word": "CUT",
      "score": 90
    },
    {
      "word": "CAT",
      "score": 80
    },
    {
      "word": "CDT",
      "score": 50
    },
    {
      "word": "CST",
      "score": 50
    },
    {
      "word": "CIT",
      "score": 40
    },
    {
      "word": "CRT",
      "score": 40
    }
  ]
}
GET /words?pattern=Л_НА&lang=ru
{
  "words": [
    {
      "word": "ЛУНА",
      "score": 70
    }
  ]
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

post/words/scoresread

Score a batch of words

Get a score for each known word. Response keys match the words you sent. Unknown words are left out instead of getting a score of 0. This lets you tell the difference between an unknown word and a weak word.

Request body

PropertyTypeNotes
wordsrequiredarray of string

1–200 items

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

basic · application/json
{
  "words": [
    "CAT",
    "ESNE",
    "ZZTOP"
  ],
  "language": "en"
}

Response

200 Score per known word.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
scoresrequiredobject<string, integer>

Word → score. Unknown words are omitted.

ZZTOP is not in the index, so it is absent
{
  "scores": {
    "CAT": 80,
    "ESNE": 25
  }
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

Clues

Find saved clues or create new AI clues.

get/clues/{word}read

Clues for one answer

Find clues for one answer. The best clues come first. Repeated clues are removed.

Parameters

NameInTypeNotes
wordrequiredpathstring

The answer. Normalized (uppercased, accents folded per language) server-side.

max 23 characters · e.g. SUB

languagequerystring

default "en" · one of 25 values

limitqueryinteger

default 10 · 1–50

Response

200 Clues for the answer, best first. An answer with no clues returns an empty list.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
cluesrequiredarray of object
textrequiredstring

The clue itself.

0–150 characters

sourcerequiredstring

Where the clue came from: published, dictionary, original, or ai. Only a new, unsaved ai clue includes a token.

one of "published", "dictionary", "original", "ai"

pubCountinteger

How many times a published clue appeared in print. Other clue types return 0.

tokenstring

Proof that this API created a new AI clue. Keep it with the clue if you publish the puzzle. Saved clues leave this field out.

GET /clues/SUB?language=en&limit=3
{
  "clues": [
    {
      "text": "Submarine sandwich",
      "source": "published",
      "pubCount": 118
    },
    {
      "text": "Fill-in teacher",
      "source": "published",
      "pubCount": 64
    },
    {
      "text": "Stand-in",
      "source": "original",
      "pubCount": 0
    }
  ]
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

post/clues/bulkread

Clues for many answers

Find clues for up to 500 answers in one request. The API returns up to five clues per answer. Answers with no clues are left out. Response keys may be uppercased or cleaned up for the language.

Request body

PropertyTypeNotes
wordsrequiredarray of string

1–500 items

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

afterFill · application/json
{
  "words": [
    "SUB",
    "OREO",
    "QWERTYX"
  ],
  "language": "en"
}

Response

200 Clues keyed by normalized answer.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
cluesrequiredobject<string, array of object>
QWERTYX has no clues, so it is absent
{
  "clues": {
    "SUB": [
      {
        "text": "Submarine sandwich",
        "source": "published",
        "pubCount": 118
      }
    ],
    "OREO": [
      {
        "text": "Cookie with a creme center",
        "source": "published",
        "pubCount": 402
      }
    ]
  }
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

post/clues/generatesolve

Generate AI clues for an answer

Write new clues for one answer with AI. This feature needs a paid plan and the solve scope. The Free plan returns 403 MEMBERS_ONLY.

Each clue includes a token that proves this API made it. This request does not save the clue. A clue may be added to the shared collection after you publish a puzzle that uses it.

X-Ai-Clues-Remaining shows how many AI clue requests are left this month. A value of -1 means there is no limit.

Request body

PropertyTypeNotes
wordrequiredstring

2–23 characters

countinteger

default 5 · 1–10

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

basic · application/json
{
  "word": "LIGHTHOUSE",
  "count": 3,
  "language": "en"
}

Response

200 Freshly written clue candidates.

Headers: X-Ai-Clues-Remaining, X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
cluesrequiredarray of object
textrequiredstring

The clue itself.

0–150 characters

sourcerequiredstring

Where the clue came from: published, dictionary, original, or ai. Only a new, unsaved ai clue includes a token.

one of "published", "dictionary", "original", "ai"

pubCountinteger

How many times a published clue appeared in print. Other clue types return 0.

tokenstring

Proof that this API created a new AI clue. Keep it with the clue if you publish the puzzle. Saved clues leave this field out.

remaininginteger

AI clues left this month; -1 for unlimited tiers.

basic · application/json
{
  "clues": [
    {
      "text": "Beacon on a rocky point",
      "source": "ai",
      "pubCount": 0,
      "token": "3f1c…"
    },
    {
      "text": "Keeper's workplace",
      "source": "ai",
      "pubCount": 0,
      "token": "9ab2…"
    }
  ],
  "remaining": 247
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE (key lacks solve), or MEMBERS_ONLY (Free tier).

  • 429

    AI_QUOTA_REACHED (monthly allowance spent) or RATE_LIMITED.

  • 502

    The solver or word-index service could not be reached, or the language index could not be loaded.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

Solver

Fill a grid, clean it up, or stop a fill.

post/fillsolve

Auto-fill a grid

Fill the empty cells while keeping all black cells and fixed letters. Most 15×15 grids take 1 to 30 seconds.

Choose the response with the Accept header:

  • application/json waits for the final result.
  • text/event-stream sends live session, progress, complete, and error events.

min_score sets the word quality goal. The default is 40. The solver may use a lower score when a grid is hard to finish. Check quality.rough for those words. Send a rough fill to POST /fill/improve to try for cleaner words.

A fill can return 200 without solving the grid. Check whether slotsFilled equals slotsTotal. When nothing is filled, reason explains why. A server failure returns 502 UPSTREAM_UNAVAILABLE.

Free accounts can fill grids up to 13×13. Paid plans can fill up to 23×23. A fill or clean-up uses one monthly fill. This is true even when the solver finds no answer. X-Fill-Quota-Remaining shows the balance.

Chinese, Japanese, and Korean need criss-cross grids. The solver cannot fill dense grids in those languages.

Parameters

NameInTypeNotes
Acceptheaderstring

application/json (default) or text/event-stream.

default "application/json" · one of application/json, text/event-stream

Request body

PropertyTypeNotes
gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

min_scoreinteger

Word quality goal. The default is 40. The solver may use lower scores when a grid is hard to finish. Check quality.rough after the fill.

default 40 · 0–1000

max_timenumber

How many seconds the solver may work. The maximum is 30 seconds.

default 25 · 1–30

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

try_hard_gridboolean

Keep trying until max_time ends. Use this when a grid you know is possible returns too_difficult. Hard grids may use the full time.

default false

A 5×5 with two black squares and one theme letter
{
  "grid": [
    "..#..",
    ".....",
    "..A..",
    ".....",
    "..#.."
  ],
  "min_score": 40,
  "max_time": 25,
  "language": "en"
}
A Hebrew grid (data model stays logical/LTR)
{
  "grid": [
    "...#.",
    ".....",
    ".#ש..",
    ".....",
    ".#..."
  ],
  "language": "he"
}

Response

200 The completed fill (JSON), or the progress stream (SSE).

Headers: X-Fill-Session-Id, X-Fill-Quota-Remaining, X-RateLimit-Limit, X-RateLimit-Remaining

application/json

The fill result. Check whether slotsFilled equals slotsTotal. A valid request can return 200 even when the solver found no fill. Server failures return 502.

PropertyTypeNotes
gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

slotsFilledrequiredinteger

Entries the solver placed.

slotsTotalrequiredinteger

Entries the grid has. Equal to slotsFilled on a complete fill; a smaller slotsFilled is a partial.

qualityrequiredobject

A report on the fill. scored is the number of words the API checked. rough lists weak words, worst first. An empty list means the fill is clean. Use POST /fill/improve when the list is not empty.

scoredrequiredinteger
roughrequiredarray of object
wordrequiredstring
scorerequiredinteger
rowrequiredinteger

Zero-based row of the entry's first cell.

colrequiredinteger

Zero-based column of the entry's first cell.

numberrequiredinteger

The clue number at that cell.

directionrequiredstring

one of "across", "down"

reasonstring

Present when no cells were filled. too_difficult means the solver stopped early. no_solution means time ran out. cancelled means a cancel request stopped the fill.

one of "too_difficult", "no_solution", "cancelled"

sessionIdstring

Also returned in X-Fill-Session-Id. Usable with POST /fill/cancel.

The `mini` request above, filled
{
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "slotsFilled": 8,
  "slotsTotal": 8,
  "sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T",
  "quality": {
    "scored": 8,
    "rough": []
  }
}
A fill the solver had to settle for
{
  "grid": [
    "IF#JG",
    "MANOR",
    "PIQUE",
    "ERASE",
    "LY#TK"
  ],
  "slotsFilled": 8,
  "slotsTotal": 8,
  "sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T",
  "quality": {
    "scored": 8,
    "rough": [
      {
        "word": "NQA",
        "score": 35,
        "row": 1,
        "col": 2,
        "number": 6,
        "direction": "down"
      }
    ]
  }
}
A well-formed grid the solver could not fill
{
  "grid": [
    "..#..",
    ".....",
    "..A..",
    ".....",
    "..#.."
  ],
  "slotsFilled": 0,
  "slotsTotal": 8,
  "reason": "too_difficult",
  "sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T",
  "quality": {
    "scored": 0,
    "rough": []
  }
}

text/event-stream

One event from a streaming fill. Events arrive in this order: session, zero or more progress events, then complete or error. The first event includes the ID needed to cancel the fill.

type: "session"
PropertyTypeNotes
typerequired"session"
sessionIdrequiredstring
type: "progress"

The solver's best grid so far. A later event may show fewer filled cells because the solver backed up. Always display the newest event.

PropertyTypeNotes
typerequired"progress"
filledrequiredinteger
totalrequiredinteger
fillrequiredobject<string, string>

Letters keyed by "row,col" (zero-based).

type: "complete"
PropertyTypeNotes
typerequired"complete"
gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

fillobject<string, string>

Letters keyed by "row,col" (zero-based).

slotsFilledrequiredinteger
slotsTotalrequiredinteger
qualityrequiredobject

A report on the fill. scored is the number of words the API checked. rough lists weak words, worst first. An empty list means the fill is clean. Use POST /fill/improve when the list is not empty.

scoredrequiredinteger
roughrequiredarray of object
wordrequiredstring
scorerequiredinteger
rowrequiredinteger

Zero-based row of the entry's first cell.

colrequiredinteger

Zero-based column of the entry's first cell.

numberrequiredinteger

The clue number at that cell.

directionrequiredstring

one of "across", "down"

type: "error"

The fill stopped. Use reason to learn why: too_difficult, no_solution, cancelled, or internal.

PropertyTypeNotes
typerequired"error"
reasonrequiredstring

one of "too_difficult", "no_solution", "cancelled", "internal"

messagestring

Human-readable detail. English only; do not show it to end users verbatim.

The event sequence (each line is one `data:` frame)
data: {"type":"session","sessionId":"kKq2x9Wn0ZqQm5Yc1Lr7Hs3T"}

data: {"type":"progress","filled":2,"total":8,"fill":{"0,0":"K","1,0":"N","2,0":"O","3,0":"L","4,0":"L","0,1":"D","1,1":"I","2,1":"V","3,1":"E","4,1":"R"}}

data: {"type":"complete","grid":["KD#MC","NINER","OVATE","LEGOS","LR#OT"],"fill":{"0,0":"K","0,1":"D"},"slotsFilled":8,"slotsTotal":8,"quality":{"scored":8,"rough":[]}}

Failures

  • 400

    VALIDATION_ERROR (bad grid) or LANGUAGE_UNAVAILABLE.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE, or GRID_SIZE_LOCKED when the grid is larger than the caller's tier allows.

  • 429

    RATE_LIMITED, FILL_QUOTA_REACHED (monthly fills spent), or SOLVER_BUSY (too many solves already running; retry shortly).

  • 502

    UPSTREAM_UNAVAILABLE: the solver failed or the stream broke. A normal solve that finds nothing returns 200 with reason set.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

post/fill/improvesolve

Clean up a filled grid

Try to replace weak words with better ones. The black cells do not change. The solver may rebuild much of the fill, so many words can change at once.

Put theme cells in locked. Those cells will not change. Unknown words are treated as your own content and are also left alone.

improved: false means the fill was already clean or the remaining weak words cannot be changed. A clean-up uses one monthly fill.

Request body

PropertyTypeNotes
gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

lockedarray of string

Cells the clean-up must not change. Write each one as "row,col", with counting from 0. Lock every cell in a theme answer to protect the answer.

0–529 items

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

max_timenumber

Wall-clock seconds for the clean-up pass.

default 25 · 1–30

The grid `POST /fill` reported `NQA` in, with the theme `Q` locked
{
  "grid": [
    "IF#JG",
    "MANOR",
    "PIQUE",
    "ERASE",
    "LY#TK"
  ],
  "locked": [
    "2,2"
  ],
  "language": "en",
  "max_time": 25
}

Response

200 The clean-up result.

Headers: X-Fill-Quota-Remaining, X-RateLimit-Limit, X-RateLimit-Remaining

application/json

The clean-up result. improved: false with no grid means nothing changed. The fill was already clean, or locked cells forced the weak word.

PropertyTypeNotes
improvedrequiredboolean
replacedinteger

How many entries changed. Present when improved is true.

gridarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

qualityrequiredobject

A report on the fill. scored is the number of words the API checked. rough lists weak words, worst first. An empty list means the fill is clean. Use POST /fill/improve when the list is not empty.

scoredrequiredinteger
roughrequiredarray of object
wordrequiredstring
scorerequiredinteger
rowrequiredinteger

Zero-based row of the entry's first cell.

colrequiredinteger

Zero-based column of the entry's first cell.

numberrequiredinteger

The clue number at that cell.

directionrequiredstring

one of "across", "down"

The `cleanup` request above, cleaned
{
  "improved": true,
  "replaced": 8,
  "grid": [
    "PA#PK",
    "EVIAN",
    "TOQUE",
    "AISLE",
    "LD#AS"
  ],
  "quality": {
    "scored": 8,
    "rough": []
  }
}
Nothing rough, or the junk is forced
{
  "improved": false,
  "quality": {
    "scored": 8,
    "rough": [
      {
        "word": "NQA",
        "score": 35,
        "row": 1,
        "col": 2,
        "number": 6,
        "direction": "down"
      }
    ]
  }
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE or GRID_SIZE_LOCKED.

  • 429

    RATE_LIMITED, FILL_QUOTA_REACHED, or SOLVER_BUSY.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

post/fill/cancelsolve

Cancel a running fill

Stop a streaming POST /fill. Get the ID from the first session event. It also appears in the X-Fill-Session-Id header.

A finished or unknown ID still returns 200. Nothing changes.

Request body

PropertyTypeNotes
session_idrequiredstring

1–128 characters

cancel · application/json
{
  "session_id": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T"
}

Response

200 Cancellation recorded.

application/json

PropertyTypeNotes
cancelledrequiredboolean
sessionIdrequiredstring
ok · application/json
{
  "cancelled": true,
  "sessionId": "kKq2x9Wn0ZqQm5Yc1Lr7Hs3T"
}

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

  • 503

    The solver or word-index service could not be reached, or the language index could not be loaded.

Puzzles

Save, publish, and export puzzles.

get/puzzlesread

List your puzzles

List the account's drafts and published puzzles. The newest edit comes first. This endpoint does not list other people's puzzles.

Parameters

NameInTypeNotes
statusquerystring

Filter by lifecycle state.

one of draft, published

limitqueryinteger

default 50 · 1–100

offsetqueryinteger

default 0 · 0–∞

Response

200 Your puzzles.

Headers: X-RateLimit-Limit, X-RateLimit-Remaining

application/json

PropertyTypeNotes
puzzlesrequiredarray of object
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

totalrequiredinteger
oneDraft · application/json
{
  "total": 1,
  "puzzles": [
    {
      "id": "k3n8q1zp",
      "title": "Coastal Mini",
      "author": "Tex",
      "language": "en",
      "size": 5,
      "grid": [
        "KD#MC",
        "NINER",
        "OVATE",
        "LEGOS",
        "LR#OT"
      ],
      "clues": {
        "across": {
          "5": "San Francisco footballer, informally",
          "7": "Egg-shaped",
          "8": "Bricks from Billund"
        },
        "down": {
          "1": "Grassy mound",
          "2": "One off the high board",
          "3": "2017 hashtag movement",
          "4": "Wave's high point",
          "6": "Pester"
        }
      },
      "themeWords": [
        "CREST"
      ],
      "status": "draft",
      "showcaseStatus": null,
      "adultThemes": false,
      "difficulty": null,
      "createdAt": "2026-09-15T10:04:00Z",
      "updatedAt": "2026-09-15T10:22:31Z"
    }
  ]
}

Failures

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 429

    Per-minute rate limit exceeded for this key's tier.

post/puzzlespuzzles:write

Create a puzzle

Save a new puzzle to the account. Do not send size; the API gets it from the grid. The account plan controls the largest allowed grid.

A new puzzle is a draft unless you send publish: true. To publish, the puzzle needs a title and a clue for every finished answer. If it is not ready, the API returns 400 VALIDATION_ERROR and saves nothing.

publish: true makes an unlisted public puzzle. It does not submit the puzzle to the showcase. Use POST /puzzles/{id}/publish with submitToShowcase: true for that. Unlisted puzzles are hidden from search engines unless you set noIndex: false.

Use Idempotency-Key to prevent copies when a request is sent twice. Reuse the same value for the same puzzle. A repeat returns the first puzzle with 200 and Idempotent-Replayed: true.

Creating a new puzzle uses one monthly puzzle. Repeats, edits, and publishing are free. X-Puzzle-Quota-Remaining shows the balance.

Parameters

NameInTypeNotes
Idempotency-Keyheaderstring

A 1 to 255 character value that is unique to this puzzle, such as a UUID or CMS post ID. Reusing it returns the first puzzle instead of making a copy. These values do not expire.

max 255 characters · e.g. post-4821

Request body

A new puzzle. Do not send size; it comes from the grid. The request body can be up to 1 MB.

PropertyTypeNotes
titlerequiredstring

1–200 characters

authorstring

0–100 characters

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesobject
acrossobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsarray of string
writeupstring

Note from the puzzle maker. Text over 2,000 characters is cut short.

0–2000 characters

showProfileboolean
noIndexboolean
publishboolean

Create a public, unlisted puzzle. The puzzle needs a title and a clue for every finished answer. An invalid puzzle is not saved. Use POST /puzzles/{id}/publish to ask for showcase review.

default false

A draft, clues still partial
{
  "title": "Coastal Mini",
  "author": "Tex",
  "language": "en",
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally"
    },
    "down": {
      "1": "Grassy mound"
    }
  },
  "themeWords": [
    "CREST"
  ]
}
Published on creation, so every entry needs a clue
{
  "title": "Coastal Mini",
  "author": "Tex",
  "language": "en",
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally",
      "7": "Egg-shaped",
      "8": "Bricks from Billund"
    },
    "down": {
      "1": "Grassy mound",
      "2": "One off the high board",
      "3": "2017 hashtag movement",
      "4": "Wave's high point",
      "6": "Pester"
    }
  },
  "themeWords": [
    "CREST"
  ],
  "publish": true
}

Response

200 This `Idempotency-Key` was already used. The API returns the first puzzle and does not read the new request body.

Headers: Idempotent-Replayed

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

201 The stored puzzle.

Headers: X-Puzzle-Quota-Remaining

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

The `draft` request above, stored
{
  "id": "k3n8q1zp",
  "title": "Coastal Mini",
  "author": "Tex",
  "language": "en",
  "size": 5,
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally"
    },
    "down": {
      "1": "Grassy mound"
    }
  },
  "themeWords": [
    "CREST"
  ],
  "status": "draft",
  "showcaseStatus": null,
  "adultThemes": false,
  "difficulty": null,
  "createdAt": "2026-09-15T10:04:00Z",
  "updatedAt": "2026-09-15T10:04:00Z"
}

Failures

  • 400

    VALIDATION_ERROR. With publish: true this also covers the publish-readiness check: a missing title, or an entry with no clue. Nothing is stored when it fails.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE or GRID_SIZE_LOCKED.

  • 413

    The request body exceeds 1 MB.

  • 429

    RATE_LIMITED, or PUZZLE_QUOTA_REACHED when no new puzzles are left this month. The error includes the plan and next step.

get/puzzles/{id}read

Get one puzzle

Get one of your puzzles, or any published puzzle, by ID. Someone else's draft returns 404. The API does not reveal whether that draft exists.

Parameters

NameInTypeNotes
idrequiredpathstring

The puzzle's opaque 8-character id.

e.g. k3n8q1zp

Response

200 The puzzle.

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

published · application/json
{
  "id": "k3n8q1zp",
  "title": "Coastal Mini",
  "author": "Tex",
  "language": "en",
  "size": 5,
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally",
      "7": "Egg-shaped",
      "8": "Bricks from Billund"
    },
    "down": {
      "1": "Grassy mound",
      "2": "One off the high board",
      "3": "2017 hashtag movement",
      "4": "Wave's high point",
      "6": "Pester"
    }
  },
  "themeWords": [
    "CREST"
  ],
  "status": "published",
  "showcaseStatus": "approved",
  "adultThemes": false,
  "difficulty": "medium",
  "createdAt": "2026-09-15T10:04:00Z",
  "updatedAt": "2026-09-15T11:00:00Z",
  "url": "https://crossword.texs.org/puzzle/k3n8q1zp",
  "embedUrl": "https://crossword.texs.org/embed/k3n8q1zp"
}

Failures

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 404

    No such puzzle, or it is a draft you do not own.

  • 429

    Per-minute rate limit exceeded for this key's tier.

patch/puzzles/{id}puzzles:write

Update a puzzle

Change one or more fields on a puzzle you own. Fields you leave out do not change. The API saves the old version first. Editing a published puzzle also runs the safety check again.

Take care with clues: it replaces the full clue set. It does not merge with old clues. If you send clues, include every Across and Down clue you want to keep.

Parameters

NameInTypeNotes
idrequiredpathstring

The puzzle's opaque 8-character id.

e.g. k3n8q1zp

Request body

Send only the fields you want to change. Other fields stay the same. The exception is clues: sending it replaces the full clue set.

PropertyTypeNotes
titlestring

1–200 characters

authorstring

0–100 characters

languagestring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

gridarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesobject
acrossobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsarray of string
writeupstring

Note from the puzzle maker. Text over 2,000 characters is cut short.

0–2000 characters

showProfileboolean
noIndexboolean
Retitle without touching the grid
{
  "title": "Seaside Mini"
}
Reword 7-Across and 4-Down (the whole clue set is resent)
{
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally",
      "7": "Shaped like an egg",
      "8": "Bricks from Billund"
    },
    "down": {
      "1": "Grassy mound",
      "2": "One off the high board",
      "3": "2017 hashtag movement",
      "4": "Top of a swell",
      "6": "Pester"
    }
  }
}

Response

200 The updated puzzle.

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

Failures

  • 400

    The request body or query string did not validate.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE or GRID_SIZE_LOCKED.

  • 404

    No such puzzle, or it is a draft you do not own.

  • 429

    Per-minute rate limit exceeded for this key's tier.

delete/puzzles/{id}puzzles:write

Delete a puzzle

Permanently delete a puzzle you own. This also deletes its version history. The API cannot undo this action.

Parameters

NameInTypeNotes
idrequiredpathstring

The puzzle's opaque 8-character id.

e.g. k3n8q1zp

Response

204 Deleted.

Failures

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 404

    No such puzzle, or it is a draft you do not own.

  • 429

    Per-minute rate limit exceeded for this key's tier.

post/puzzles/{id}/publishpuzzles:write

Publish a puzzle

Publish a draft and create its public page and embed URL. Publishing also runs a safety check. The puzzle's clues may be added to the shared clue collection under the Terms of Use.

The puzzle is unlisted unless you send submitToShowcase: true. A person reviews every API showcase request; nothing is listed automatically, so send your best work. An unlisted puzzle is hidden from search engines unless you set noIndex: false.

An account can have up to three puzzles waiting for showcase review. A fourth returns 409 SHOWCASE_QUEUE_FULL and changes nothing.

You may publish an already published puzzle again. This updates the optional fields and runs the safety check again. A puzzle already in the review queue keeps its place.

Parameters

NameInTypeNotes
idrequiredpathstring

The puzzle's opaque 8-character id.

e.g. k3n8q1zp

Request body (optional)

PropertyTypeNotes
writeupstring

Note shown on the puzzle page. Text over 2,000 characters is cut short.

0–2000 characters

showProfileboolean

Link this puzzle from your public author page.

noIndexboolean

Hide the page from search engines. The share link still works.

submitToShowcaseboolean

Enter the public showcase review queue. Off by default.

default false

publish · application/json
{
  "writeup": "My first API-built puzzle.",
  "showProfile": true,
  "submitToShowcase": true
}

Response

200 The published puzzle.

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

Submitted to the showcase, scanned clean
{
  "id": "k3n8q1zp",
  "title": "Coastal Mini",
  "author": "Tex",
  "language": "en",
  "size": 5,
  "grid": [
    "KD#MC",
    "NINER",
    "OVATE",
    "LEGOS",
    "LR#OT"
  ],
  "clues": {
    "across": {
      "5": "San Francisco footballer, informally",
      "7": "Egg-shaped",
      "8": "Bricks from Billund"
    },
    "down": {
      "1": "Grassy mound",
      "2": "One off the high board",
      "3": "2017 hashtag movement",
      "4": "Wave's high point",
      "6": "Pester"
    }
  },
  "themeWords": [
    "CREST"
  ],
  "status": "published",
  "showcaseStatus": "pre_approved",
  "adultThemes": false,
  "difficulty": null,
  "writeup": "My first API-built puzzle.",
  "showProfile": true,
  "createdAt": "2026-09-15T10:04:00Z",
  "updatedAt": "2026-09-15T12:31:00Z",
  "url": "https://crossword.texs.org/puzzle/k3n8q1zp",
  "embedUrl": "https://crossword.texs.org/embed/k3n8q1zp"
}

Failures

  • 400

    VALIDATION_ERROR: the puzzle needs a title and a clue for every finished answer. Unfinished answers do not need clues yet.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    FORBIDDEN_SCOPE or GRID_SIZE_LOCKED.

  • 404

    No such puzzle, or it is a draft you do not own.

  • 409

    SHOWCASE_QUEUE_FULL: three puzzles are already waiting for review. Nothing was changed.

  • 429

    Per-minute rate limit exceeded for this key's tier.

get/puzzles/{id}/exportread

Export a puzzle

Download a puzzle as JSON or as an Across Lite .puz file.

JSON matches GET /puzzles/{id}. The .puz format only supports some Latin-script languages. Check puzExportable in GET /languages.

Parameters

NameInTypeNotes
idrequiredpathstring

The puzzle's opaque 8-character id.

e.g. k3n8q1zp

formatquerystring

Output format.

default "json" · one of json, puz

Response

200 The exported puzzle.

Headers: Content-Disposition

application/json

A saved puzzle. size comes from grid and cannot be changed directly.

PropertyTypeNotes
idrequiredstring

matches ^[a-z0-9]{8}$

titlerequiredstring

0–200 characters

authorrequiredstring

The name printed on the puzzle. It does not have to match the account name.

0–100 characters

languagerequiredstring

Puzzle language code. You may send pt-BR, but the API returns pt. Both use the same Portuguese data.

default "en" · one of 25: "en", "es", "fr", "de", "it", "pt", "pt-BR", "pl"

sizerequiredinteger

3–23 · read-only

gridrequiredarray of string

A square grid made of text rows, starting with the top row:

  • .: an empty white cell the solver may fill
  • #: a black square
  • anything else: a fixed letter

A grid must be 3 to 23 cells wide, with the same number of rows. Some visible letters use more than one Unicode character. Count visible letters with a grapheme tool such as Intl.Segmenter, not JavaScript .length.

The API may uppercase or clean up letters for the language. The returned text may not exactly match the bytes you sent.

3–23 items

cluesrequiredobject
acrossrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

downrequiredobject<string, string>

Clue text keyed by clue number. Across and Down use the numbers printed on the grid.

themeWordsrequiredarray of string

Theme answers to highlight on the puzzle page.

statusrequiredstring

one of "draft", "published"

showcaseStatusrequiredstring | null

The puzzle's showcase review state. null means it was not submitted. A normal review moves from pending to pre_approved, then to approved. A blocked word moves it to flagged for a person to check.

one of "pending", "pre_approved", "flagged", "approved", "rejected", null

adultThemesrequiredboolean

true when the safety check found adult themes. The public page has an age check, and the embed will not load. Add the same protection if you show the puzzle on your own site.

read-only

difficultyrequiredstring | null

How hard the puzzle is to solve. The rating looks at clue style and word difficulty. Drafts return null. A new publish may also return null while the rating is being made. Editing clues makes a new rating.

one of "easy", "medium", "hard", "expert", null · read-only

writeupstring

Constructor's note shown on the puzzle page.

showProfileboolean

Whether this puzzle is linked from the author's public page.

noIndexboolean

Author asked search engines to skip the puzzle page.

createdAtrequiredstring (date-time)
updatedAtrequiredstring (date-time)
urlstring (uri)

Public puzzle page. Present once status is published.

read-only

embedUrlstring (uri)

Chrome-less frame for embedding. Present once status is published.

read-only

application/x-crossword

Across Lite .puz binary.

string (binary)

Failures

  • 400

    VALIDATION_ERROR: .puz requested for a language that cannot be encoded in ISO-8859-1.

  • 401

    Missing, malformed, unknown or revoked key.

  • 403

    The key is valid but lacks the scope this operation needs.

  • 404

    No such puzzle, or it is a draft you do not own.

  • 429

    Per-minute rate limit exceeded for this key's tier.

Languages

The API supports 24 puzzle languages. Each one has its own word list. Call GET /languages for the current list instead of saving the list in your code.

Chinese, Japanese, and Korean only support criss-cross grids. Arabic and Hebrew display from right to left. .puz export only works for languages supported by Latin-1.

CodeLanguageAutofillDirection.puz
enEnglish · EnglishDenseLTRYes
esSpanish · EspañolDenseLTRYes
frFrench · FrançaisDenseLTRYes
deGerman · DeutschDenseLTRYes
itItalian · ItalianoDenseLTRYes
ptPortuguese · PortuguêsDenseLTRYes
plPolish · PolskiDenseLTRNo
nlDutch · NederlandsDenseLTRYes
zhChinese · 中文Criss-cross onlyLTRNo
jaJapanese · 日本語Criss-cross onlyLTRNo
koKorean · 한국어Criss-cross onlyLTRNo
hiHindi · हिन्दी(not yet deployed)DenseLTRNo
arArabic · العربيةDenseRTLNo
trTurkish · TürkçeDenseLTRNo
heHebrew · עבריתDenseRTLNo
idIndonesian · Bahasa IndonesiaDenseLTRYes
csCzech · ČeštinaDenseLTRNo
ukUkrainian · УкраїнськаDenseLTRNo
roRomanian · RomânăDenseLTRNo
ruRussian · РусскийDenseLTRNo
svSwedish · SvenskaDenseLTRYes
noNorwegian · Norsk(not yet deployed)DenseLTRYes
daDanish · Dansk(not yet deployed)DenseLTRYes
hrCroatian · HrvatskiDenseLTRNo

You may send pt-BR, but the API returns pt. Both forms of Portuguese share one word and clue database. A language marked “not yet deployed” cannot be used for search or fill. Those requests return LANGUAGE_UNAVAILABLE.

About the data licenses

The word lists and clue collection use data from several open projects. Each project has its own license. If you share this data, you must follow those licenses. The API does not offer a bulk data download.

Word lists

LanguageSourceLicence
EnglishCrossword Nexus Collaborative Word List · ENABLEMIT · public domain
Spanishdoozan/spanish_data: Wiktionary headwords and frequency ranksCC BY-SA
22 others · frequency componentFrequencyWords: OpenSubtitles 2018 word countsCC BY-SA 4.0
22 others · dictionary componentHunspell spelling dictionaries via wooorm/dictionaries, plus JMdict readings for Japanese and Shreeshrii Hindi Hunspell for HindiPer language (see below)

Each language database records its own sources and licenses. These include MIT, BSD, MPL, LGPL, GPL, AGPL, and CC BY-SA. Chinese, Arabic, and Indonesian use frequency data only. Seedocs/word-list-sources.md for the full list.

The full non-English data set is not covered by the MIT license. Only some sources use MIT.

Clue corpus

The source field tells you where a clue came from.published means it appeared in a published crossword.dictionary means it was adapted from a licensed definition. Sources include Wiktionary editions (through Kaikki/Wiktextract and DBnary), Open English WordNet and Wikidata Lexemes. Their licenses may be CC BY, CC BY-SA, or CC0. See the exact list at /api/clues/sources.original means the clue was written for this service. We may add clues from published puzzles to the collection. See the Terms of Use.

Your puzzles

You own the puzzles you build. Publishing creates a public page and embed frame. A puzzle only enters showcase review when you sendsubmitToShowcase: true. See the terms for full details.

Changelog

VersionDateChange
1.0.02026-09First public version. It includes 17 operations for words, clues, fills, and puzzles. It also includes thexword client, CLI, MCP server, Markdown docs, and OpenAPI file.

Version 1 may gain new endpoints, optional fields, and error codes. Your app should ignore response fields it does not know. Treat an unknown code like any other error with the same HTTP status. A breaking change will use a new version in the URL.

Contract v1.0.0 · 17 operations · base URL https://crossword.texs.org/api/v1. The reference comes from the OpenAPI file used by the server.

Terms·Privacy·texs.org