{
  "info": {
    "_postman_id": "54c34fd6-3c25-46a5-a390-8e0d29f51724",
    "name": "Wordly Vocabulary API",
    "description": "Production-ready requests and automated tests for every Wordly API v1 endpoint. Import the Wordly API — Test environment, paste only your API key, select the environment, then run requests. Authenticated requests consume the documented credits; the Random example uses 3 credits and Batch cost depends on returned records.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{api_key}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "pm.test('Response time is below 3 seconds', function () { pm.expect(pm.response.responseTime).to.be.below(3000); });",
          "pm.test('Response is JSON', function () { pm.expect(pm.response.headers.get('Content-Type') || '').to.include('application/json'); });",
          "pm.test('Security headers are present', function () { pm.expect(pm.response.headers.get('X-Content-Type-Options')).to.eql('nosniff'); });",
          "var body = {}; try { body = pm.response.json(); } catch (e) {}",
          "pm.test('Response uses data or error envelope', function () { pm.expect(body).to.satisfy(function (value) { return Object.prototype.hasOwnProperty.call(value, 'data') || Object.prototype.hasOwnProperty.call(value, 'error'); }); });",
          "if (body.meta && body.meta.request_id) { pm.collectionVariables.set('last_request_id', body.meta.request_id); }",
          "if (body.data && typeof body.data.credits_remaining === 'number') { pm.collectionVariables.set('last_credits_remaining', String(body.data.credits_remaining)); }"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "last_request_id",
      "value": ""
    },
    {
      "key": "last_credits_remaining",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "01 — Public endpoints",
      "description": "No API key and no credits required.",
      "item": [
        {
          "name": "Service status",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/status?lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "status"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Health and version check. Cost: 0 credits."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Service is operational', function () { pm.expect(json.data.status).to.eql('ok'); pm.expect(json.data.version).to.eql('v1'); });",
                  "pm.test('No credits are charged', function () { pm.expect(json.meta).to.not.have.property('credits_used'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Supported languages",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/languages?lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "languages"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Lists supported UI and API-message locales. Cost: 0 credits."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('30 locale records are returned', function () { pm.expect(json.data).to.be.an('array').with.lengthOf(30); pm.expect(json.meta.count).to.eql(30); });",
                  "pm.test('Locale records have required fields', function () { json.data.forEach(function (item) { pm.expect(item).to.have.all.keys('code', 'name', 'message_localization', 'vocabulary_translation'); }); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "02 — Authentication and balance",
      "item": [
        {
          "name": "Validate key and get balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account?lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Validates the environment API key and returns the remaining balance. Cost: 0 credits."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "exec": [
                  "if (!pm.environment.get('api_key')) { throw new Error('Set api_key in the Wordly API — Test environment first.'); }"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Balance is a non-negative number', function () { pm.expect(json.data.credits_remaining).to.be.a('number').and.at.least(0); });",
                  "pm.test('Balance lookup is free', function () { pm.expect(json.meta).to.not.have.property('credits_used'); });",
                  "pm.test('Balance header exists only here', function () { pm.expect(pm.response.headers.has('X-Credits-Remaining')).to.be.true; });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "03 — Vocabulary",
      "description": "Authenticated vocabulary requests. These tests consume credits.",
      "item": [
        {
          "name": "Get exact word",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/words/{{test_word}}?languages={{translation_languages}}&lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "words",
                "{{test_word}}"
              ],
              "query": [
                {
                  "key": "languages",
                  "value": "{{translation_languages}}"
                },
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Exact lookup. Cost: 1 credit for catalog/translated or 5 for enriched."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Word object is complete', function () { pm.expect(json.data).to.include.all.keys('id','word','part_of_speech','level','definition','example','phonetic','translations','synonyms','antonyms','categories','media','completeness'); });",
                  "pm.test('Completeness is valid', function () { pm.expect(['catalog','translated','enriched']).to.include(json.data.completeness); });",
                  "pm.test('Media shape is stable', function () { pm.expect(json.data.media).to.include.all.keys('image_url','audio_url','attribution'); });",
                  "pm.test('Cost matches a supported tier', function () { pm.expect([1,5]).to.include(json.meta.credits_used); });",
                  "pm.test('Remaining balance is not duplicated', function () { pm.expect(json.meta).to.not.have.property('credits_remaining'); pm.expect(pm.response.headers.has('X-Credits-Remaining')).to.be.false; });"
                ]
              }
            }
          ]
        },
        {
          "name": "Search words",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/words/search?q={{search_query}}&level={{search_level}}&languages={{translation_languages}}&limit={{search_limit}}&lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "words",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "{{search_query}}"
                },
                {
                  "key": "level",
                  "value": "{{search_level}}"
                },
                {
                  "key": "languages",
                  "value": "{{translation_languages}}"
                },
                {
                  "key": "limit",
                  "value": "{{search_limit}}"
                },
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Filtered search with cursor pagination. Cost: 1 credit."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Data is an array within requested limit', function () { pm.expect(json.data).to.be.an('array'); pm.expect(json.data.length).to.be.at.most(Number(pm.environment.get('search_limit'))); });",
                  "pm.test('Pagination metadata exists', function () { pm.expect(json.meta).to.have.property('next_cursor'); pm.expect(json.meta.count).to.eql(json.data.length); });",
                  "pm.test('One credit was used', function () { pm.expect(json.meta.credits_used).to.eql(1); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Random words",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/words/random?count={{random_count}}&level={{random_level}}&languages={{translation_languages}}&lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "words",
                "random"
              ],
              "query": [
                {
                  "key": "count",
                  "value": "{{random_count}}"
                },
                {
                  "key": "level",
                  "value": "{{random_level}}"
                },
                {
                  "key": "languages",
                  "value": "{{translation_languages}}"
                },
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Random selection for exercises. Default environment cost: 3 credits."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Random result is an array', function () { pm.expect(json.data).to.be.an('array'); pm.expect(json.data.length).to.be.at.most(Number(pm.environment.get('random_count'))); });",
                  "pm.test('Requested slot cost was used', function () { pm.expect(json.meta.credits_used).to.eql(Number(pm.environment.get('random_count'))); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Batch lookup",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"words\": [\"water\", \"opportunity\", \"not-a-real-word-xyz\"],\n  \"languages\": [\"tr\", \"de\"]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/words/batch?lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "words",
                "batch"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Ordered batch lookup. Cost depends on found record richness; minimum 1 credit."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 200', function () { pm.response.to.have.status(200); });",
                  "var json = pm.response.json();",
                  "pm.test('Request order and result shape are preserved', function () { pm.expect(json.data).to.be.an('array').with.lengthOf(3); json.data.forEach(function (item) { pm.expect(item).to.include.all.keys('query','found','word'); }); });",
                  "pm.test('Missing word is represented safely', function () { var missing = json.data.find(function (item) { return item.query === 'not-a-real-word-xyz'; }); pm.expect(missing.found).to.eql(false); pm.expect(missing.word).to.eql(null); });",
                  "pm.test('Credit charge is reported', function () { pm.expect(json.meta.credits_used).to.be.a('number').and.at.least(1); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04 — Safe negative tests",
      "description": "Expected failures that validate error handling. They do not intentionally trigger rate limits.",
      "item": [
        {
          "name": "Missing API key → 401",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account?lang={{message_language}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account"
              ],
              "query": [
                {
                  "key": "lang",
                  "value": "{{message_language}}"
                }
              ]
            },
            "description": "Confirms the stable invalid_api_key error."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 401', function () { pm.response.to.have.status(401); });",
                  "var json = pm.response.json();",
                  "pm.test('Stable error code is returned', function () { pm.expect(json.error.code).to.eql('invalid_api_key'); pm.expect(json.error.message).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Wrong batch content type → 415",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "text/plain"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "water"
            },
            "url": {
              "raw": "{{base_url}}/words/batch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "words",
                "batch"
              ]
            },
            "description": "Confirms JSON content-type enforcement. No credits are charged."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Status is 415', function () { pm.response.to.have.status(415); });",
                  "pm.test('Stable error code is returned', function () { pm.expect(pm.response.json().error.code).to.eql('unsupported_media_type'); });"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
