Add cards with the API
List your decks and their cards, add up to 100 cards at a time, and look up the custom field ids a card write needs.
Adding cards takes two steps: find the deck, then post the cards to it.
List your decks
Needs a key with the decks:read scope.
curl https://yalango.com/api/v1/decks \
-H "Authorization: Bearer yal_your_key_here"
Decks come back newest first, 50 at a time. Use ?limit= to change the page size, up to 200.
{
"decks": [
{
"doc_id": "8sKd92mfPqR1xLvBn4Tz",
"id": 48210937465,
"name": "Spanish verbs",
"description": "",
"source_ISO_639-1": "en",
"target_ISO_639-1": "es",
"privacy": "private",
"number_of_items": 214,
"tags": "",
"created_timestamp": "2026-01-14T09:22:31.000Z",
"last_updated_timestamp": "2026-03-02T18:05:12.000Z"
}
],
"next_cursor": "eyJ0cyI6MTc0MDkzMDMxMjAwMCwiZG9jX2lkIjoiOHNLZDkybWZQcVIxeEx2Qm40VHoifQ"
}
When next_cursor is not null there are more decks. Pass it back to get the following page, and keep going until it comes back null:
curl "https://yalango.com/api/v1/decks?cursor=eyJ0cyI6MTc0..." \
-H "Authorization: Bearer yal_your_key_here"
Treat the cursor as opaque - it encodes both a timestamp and a deck id, and its format may change.
Add cards
Needs a key with the cards:write scope. The doc_id in the URL is the one from the list above.
curl -X POST https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz/cards \
-H "Authorization: Bearer yal_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"cards": [
{ "source": "the dog", "target": "el perro" },
{ "source": "the cat", "target": "el gato" }
]
}'
{
"created": 2,
"card_ids": ["Lp4nVx8QmRt2YsKd", "Bq7WcZ3jHf5TnXvA"]
}
source is the side in the language you know, target the side in the language you are learning. The card's languages, privacy and author all come from the deck, so you do not set them.
Read a deck's cards
Needs a key with the cards:read scope.
curl https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz/cards \
-H "Authorization: Bearer yal_your_key_here"
{
"cards": [
{
"card_doc_id": "Lp4nVx8QmRt2YsKd",
"source": "the dog",
"target": "el perro",
"custom_fields": { "kL9mQ2xVnB4t": "masculine" },
"created_timestamp": "2026-03-02T18:05:12.000Z",
"last_updated_timestamp": "2026-03-02T18:05:12.000Z"
}
],
"next_cursor": "eyJkb2NfaWQiOiJMcDRuVng4UW1SdDJZc0tkIn0"
}
Cards come back 50 at a time, up to 200 with ?limit=. Paging works the same way as the deck list: pass next_cursor back as ?cursor= until it comes back null.
The order is stable across pages but is not chronological - it follows the internal card id, not created_timestamp. Sort on created_timestamp yourself if you need cards in the order they were added.
A deck that does not exist, or one belonging to another account, returns 404 deck_not_found or 403 deck_not_owned.
Rules for a batch
- Between 1 and 100 cards per request.
sourceandtargetare each up to 2000 characters, and at least one of the two must be non-empty.- The whole request body must be under 256 KB.
One bad card rejects the whole batch
If any card fails validation, nothing is written and no quota is used. The error names the exact card and field:
{
"error": {
"code": "invalid_card",
"message": "cards[3].target must be at most 2000 characters.",
"param": "cards[3].target",
"request_id": "req_8fKd2mXp4LvNc7Bh"
}
}
Fix the card the error names and resend the batch.
Custom fields
If your deck has custom fields, you can fill them in at the same time. The keys are the field ids, not their display names:
curl -X POST https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz/cards \
-H "Authorization: Bearer yal_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"cards": [
{
"source": "the dog",
"target": "el perro",
"custom_fields": { "kL9mQ2xVnB4t": "masculine" }
}
]
}'
To find the ids, list the deck's custom fields. This needs a key with the decks:read scope:
curl https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz/custom-fields \
-H "Authorization: Bearer yal_your_key_here"
{
"custom_fields": [
{ "id": "kL9mQ2xVnB4t", "name": "Gender", "type": "text" }
]
}
The id is what you use as the key in custom_fields on a card. The API only accepts fields that already exist on the deck - see Custom fields with the API for how to create one.
Retrying safely
Like deck creation, this endpoint accepts an Idempotency-Key header:
curl -X POST https://yalango.com/api/v1/decks/8sKd92mfPqR1xLvBn4Tz/cards \
-H "Authorization: Bearer yal_your_key_here" \
-H "Idempotency-Key: chapter-4-vocab" \
-H "Content-Type: application/json" \
-d '{"cards": [{"source": "the dog", "target": "el perro"}]}'
If a 100-card batch times out, retrying with the same key returns the original response instead of adding the cards a second time. Use a new key for each new batch. Keys are remembered for 24 hours.
What happens next
Cards added through the API are indistinguishable from cards typed into the web app. They join your vocabulary, count toward your review schedule, and appear in games straight away.
On Premium, audio is generated automatically in the background, exactly as it is for an import from the web app.
Card limits
The free plan allows 10,000 cards. A batch that would take you over is rejected with 402 quota_exceeded, and the message says how many you have and how many you can still add. Nothing is written.
For a large one-off list, the web app's import tools handle a whole file in one step.