Create and update decks with the API
Create a deck, change its name or description, and understand why decks made through the API are always private.
Both endpoints on this page need a key with the decks:write scope.
Create a deck
curl -X POST https://yalango.com/api/v1/decks \
-H "Authorization: Bearer yal_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Spanish verbs",
"description": "Verbs I keep forgetting",
"source_ISO_639-1": "en",
"target_ISO_639-1": "es"
}'
{
"deck": {
"doc_id": "8sKd92mfPqR1xLvBn4Tz",
"id": 48210937465,
"name": "Spanish verbs",
"description": "Verbs I keep forgetting",
"source_ISO_639-1": "en",
"target_ISO_639-1": "es",
"privacy": "private",
"number_of_items": 0,
"tags": "",
"created_timestamp": "2026-03-02T18:05:12.000Z",
"last_updated_timestamp": "2026-03-02T18:05:12.000Z"
}
}
Keep the doc_id - adding cards needs it.
Fields
| Field | Required | Notes |
|---|---|---|
name | Yes | Up to 100 characters |
source_ISO_639-1 | Yes | The language you already know, for example en |
target_ISO_639-1 | Yes | The language you are learning, for example es |
description | No | Up to 1000 characters |
tags | No | Up to 300 characters |
Any other field is rejected rather than ignored, so a misspelled field name fails loudly instead of quietly doing nothing.
API decks are always private
You cannot create a public deck through the API, and privacy is not something you can change later either. Publishing a deck stays a web action, on make your deck public.
There are two reasons. Publishing a deck puts it in front of other learners, which is a decision worth making deliberately rather than as a side effect of a loop in a script. And publishing requires a public profile, which the web app asks you to confirm.
A deck created through the API behaves exactly like any other private deck, so you can publish it from the web whenever you want.
Update a deck
curl -X PATCH https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz \
-H "Authorization: Bearer yal_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "Spanish verbs - B1"}'
The response is the full updated deck, in the same shape as the create response.
What you can change
name, title, description, tags, youtube_id, yalango_text_id, source_script, target_script, and the two language fields.
Send only the fields you want to change. Anything else - privacy, uid, id, number_of_items, the rating counts - is refused with 400 field_not_patchable, naming the field. That is deliberate: silently dropping a field you meant to set is worse than telling you it is not allowed.
Changing a language only works on an empty deck
If the deck already has cards, changing source_ISO_639-1 or target_ISO_639-1 fails with 400 deck_not_empty.
A card's target language is what ties it to your vocabulary and your per-language statistics. Changing a populated deck's language would leave the deck saying one thing and every card, vocabulary entry and statistic saying another. The realistic case - fixing a language you picked wrongly a moment ago - still works, because the deck is still empty.
To change the language of a deck that has cards, create a new deck and move the content across.
Retrying safely
POST /v1/decks accepts an Idempotency-Key header. If the request times out and you retry with the same key, you get the original response back instead of a second deck:
curl -X POST https://yalango.com/api/v1/decks \
-H "Authorization: Bearer yal_your_key_here" \
-H "Idempotency-Key: spanish-verbs-2026-03-02" \
-H "Content-Type: application/json" \
-d '{"name": "Spanish verbs", "source_ISO_639-1": "en", "target_ISO_639-1": "es"}'
Keys are remembered for 24 hours. Reusing one with a different body is an error rather than a replay, so a copied-and-pasted key cannot hide a request you meant to send.
Deck limits
The free plan allows 100 decks. Going over returns 402 quota_exceeded with a message saying how many you have and how many are left. Yalango Premium removes the limit.