{"info":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","description":"<html><head></head><body><p>Requests for the five endpoints of our AgriMbaza QA Evaluation Platform service, in the order the evaluation runs them. Each request carries a description, a copyable <code>curl</code> command, saved example responses, and <strong>Tests</strong> that check the response against the Team API Specification.</p>\n<h2 id=\"1-set-up\">1. Set up</h2>\n<ol>\n<li>Open the collection's <strong>Variables</strong> tab and set:<ul>\n<li><code>baseUrl</code>: the registered base URL, without a trailing slash (<code>https://api.agrimbaza.awesomity.rw</code>).</li>\n<li><code>token</code>: the bearer token we shared with you. Put it in <strong>Current value</strong> only, so it is not synced or exported.</li>\n</ul>\n</li>\n<li>Save. Every request inherits <strong>Bearer Token</strong> auth from the collection (<code>Authorization: Bearer </code>).</li>\n</ol>\n<p>For curl, set the same two values once in your shell:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">export BASE_URL=https://api.agrimbaza.awesomity.rw\nexport TOKEN='&lt;bearer token shared with you&gt;'\n</code></pre>\n<h2 id=\"2-endpoints\">2. Endpoints</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Method</th>\n<th>Path</th>\n<th>Track</th>\n<th>Returns</th>\n<th>Time limit</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>GET</td>\n<td><code>/v1/health</code></td>\n<td>all</td>\n<td>200 when ready</td>\n<td>short</td>\n</tr>\n<tr>\n<td>GET</td>\n<td><code>/v1/info</code></td>\n<td>all</td>\n<td>JSON describing the models</td>\n<td>short</td>\n</tr>\n<tr>\n<td>POST</td>\n<td><code>/v1/llm</code></td>\n<td>Simple QA, Reasoning</td>\n<td>JSON <code>{\"answer\", \"thinking\"}</code></td>\n<td>60 s / 180 s</td>\n</tr>\n<tr>\n<td>POST</td>\n<td><code>/v1/asr</code></td>\n<td>ASR</td>\n<td>JSON <code>{\"transcript\"}</code></td>\n<td>60 s</td>\n</tr>\n<tr>\n<td>POST</td>\n<td><code>/v1/tts</code></td>\n<td>TTS</td>\n<td>WAV bytes (<code>audio/wav</code>)</td>\n<td>60 s</td>\n</tr>\n</tbody>\n</table>\n</div><p>All <code>/v1</code> endpoints require the bearer token. Interactive documentation is also published at <code>https://api.agrimbaza.awesomity.rw/docs</code> (Swagger UI) and <code>https://api.agrimbaza.awesomity.rw/redoc</code>, with the schema at <code>https://api.agrimbaza.awesomity.rw/openapi.json</code>; those pages need no token.</p>\n<h2 id=\"3-running-everything\">3. Running everything</h2>\n<p>Use <strong>Run collection</strong> to send every request in order and see the test results. Select a WAV file for the ASR request first (see its description). The run makes a few billed model calls.</p>\n<p>From the command line, with <a href=\"https://www.npmjs.com/package/newman\">Newman</a>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">npx newman run AgriMbaza_QA_Evaluation_API.postman_collection.json --env-var \"baseUrl=$BASE_URL\" --env-var \"token=$TOKEN\"\n</code></pre>\n<p>(The ASR request needs a file; add <code>--folder</code> to run only some folders, or run ASR with curl.)</p>\n<h2 id=\"4-behaviour\">4. Behaviour</h2>\n<ul>\n<li><strong>One question per request</strong>, no conversation history. The service handles at least 4 concurrent requests.</li>\n<li><strong>Errors</strong> are JSON: <code>{\"error\": {\"code\": \"...\", \"message\": \"...\"}}</code>.</li>\n</ul>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Situation</th>\n<th>Status</th>\n<th>Retry?</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Missing or wrong token</td>\n<td>401</td>\n<td>no</td>\n</tr>\n<tr>\n<td>Track not enabled</td>\n<td>404</td>\n<td>no</td>\n</tr>\n<tr>\n<td>Upload too large</td>\n<td>413</td>\n<td>no</td>\n</tr>\n<tr>\n<td>Invalid request</td>\n<td>422</td>\n<td>no</td>\n</tr>\n<tr>\n<td>Provider rate limit</td>\n<td>429 + <code>Retry-After</code></td>\n<td>yes</td>\n</tr>\n<tr>\n<td>Provider rejected the service's request</td>\n<td>500</td>\n<td>yes</td>\n</tr>\n<tr>\n<td>Provider error or output that would break the contract</td>\n<td>502</td>\n<td>yes</td>\n</tr>\n<tr>\n<td>Provider unreachable / service not ready</td>\n<td>503 + <code>Retry-After</code></td>\n<td>yes</td>\n</tr>\n<tr>\n<td>Provider did not answer in time</td>\n<td>504</td>\n<td>yes</td>\n</tr>\n</tbody>\n</table>\n</div><ul>\n<li>The service never returns 200 with a body that breaks the specification; such cases return 502.</li>\n<li>Example responses saved in this collection are illustrative. Real answers, transcripts and audio come from the configured models and have not been verified as agricultural advice.</li>\n</ul>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"25741729","collectionId":"f52c8c09-c572-40d4-84f8-e49a8fddc835","publishedId":"2sBYHVuyfN","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"publishDate":"2026-10-27T14:21:51.000Z"},"item":[{"name":"1. Service","item":[{"name":"Health","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","pm.test(\"Status field is ok\", () => pm.expect(pm.response.json().status).to.equal(\"ok\"));"],"id":"1c61e1d4-ac48-44b8-8e60-f15b959c6b95"}}],"id":"005e7e67-c783-4e3d-877a-68232fb4c474","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/health","description":"<p>Returns <strong>200</strong> when the service is ready to accept evaluation requests, <strong>503</strong> otherwise.</p>\n<p>Besides the configuration, the check confirms that each configured model can be reached with the service's provider credentials (a free metadata lookup, cached for 60 s). It cannot detect an exhausted provider quota.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/health\" -H \"Authorization: Bearer $TOKEN\"\n</code></pre>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","health"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"4eb53313-d974-4b1f-a2ee-ca552fafffbc","name":"200 Ready","originalRequest":{"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/health","description":"Returns **200** when the service is ready to accept evaluation requests, **503** otherwise.\n\nBesides the configuration, the check confirms that each configured model can be reached with the service's provider credentials (a free metadata lookup, cached for 60 s). It cannot detect an exhausted provider quota.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/health\" -H \"Authorization: Bearer $TOKEN\"\n```"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"status\": \"ok\",\n  \"tracks\": [\n    \"simple_qa\",\n    \"reasoning\",\n    \"asr\",\n    \"tts\"\n  ]\n}"},{"id":"55310559-19ea-4f84-9f81-71275cb683ce","name":"503 Not ready","originalRequest":{"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/health","description":"Returns **200** when the service is ready to accept evaluation requests, **503** otherwise.\n\nBesides the configuration, the check confirms that each configured model can be reached with the service's provider credentials (a free metadata lookup, cached for 60 s). It cannot detect an exhausted provider quota.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/health\" -H \"Authorization: Bearer $TOKEN\"\n```"},"status":"Service Unavailable","code":503,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"},{"key":"Retry-After","value":"5"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"not_ready\",\n    \"message\": \"Provider check failed: llm: OpenAI rejected the request (HTTP 401).\"\n  }\n}"}],"_postman_id":"005e7e67-c783-4e3d-877a-68232fb4c474"},{"name":"Info","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const info = pm.response.json();","pm.test(\"Has provider, model_id, version and tracks\", () => {","    [\"provider\", \"model_id\", \"version\"].forEach(k => pm.expect(info[k]).to.be.a(\"string\").and.not.empty);","    pm.expect(info.tracks).to.be.an(\"array\");","});","pm.test(\"Tracks are known values\", () =>","    info.tracks.forEach(t => pm.expect(t).to.be.oneOf([\"simple_qa\", \"reasoning\", \"asr\", \"tts\"])));"],"id":"11ec8a13-6662-4174-b03f-47c6f6fd013b"}}],"id":"be582e25-ba54-4659-b317-17c3df9e0f4b","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/info","description":"<p>Identifies the models behind the service. <code>provider</code> and <code>model_id</code> describe the LLM used for <code>simple_qa</code> and <code>reasoning</code>; <code>components</code> lists the provider and model for each component, since ASR and TTS use different models. <code>tracks</code> lists the tracks this deployment serves. All values come from the running configuration.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/info\" -H \"Authorization: Bearer $TOKEN\"\n</code></pre>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","info"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"9a5965c2-bab4-4b07-bb2d-139f6bec0277","name":"200 OK","originalRequest":{"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/info","description":"Identifies the models behind the service. `provider` and `model_id` describe the LLM used for `simple_qa` and `reasoning`; `components` lists the provider and model for each component, since ASR and TTS use different models. `tracks` lists the tracks this deployment serves. All values come from the running configuration.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/info\" -H \"Authorization: Bearer $TOKEN\"\n```"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"provider\": \"openai\",\n  \"model_id\": \"gpt-6.1-terra\",\n  \"version\": \"1.0.0\",\n  \"tracks\": [\n    \"simple_qa\",\n    \"reasoning\",\n    \"asr\",\n    \"tts\"\n  ],\n  \"components\": {\n    \"llm\": {\n      \"provider\": \"openai\",\n      \"model_id\": \"gpt-6.1-terra\",\n      \"voice\": null\n    },\n    \"asr\": {\n      \"provider\": \"openai\",\n      \"model_id\": \"gpt-transcribe\",\n      \"voice\": null\n    },\n    \"tts\": {\n      \"provider\": \"openai\",\n      \"model_id\": \"gpt-4o-mini-tts\",\n      \"voice\": \"marin\"\n    }\n  }\n}"}],"_postman_id":"be582e25-ba54-4659-b317-17c3df9e0f4b"},{"name":"Authentication — missing token (expect 401)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 401\", () => pm.response.to.have.status(401));","pm.test(\"WWW-Authenticate is Bearer\", () => pm.expect(pm.response.headers.get(\"WWW-Authenticate\")).to.equal(\"Bearer\"));"],"id":"5af59a24-de5b-448f-b98d-3c1a43d87d31"}}],"id":"b478749a-94b8-461d-bb7a-872b33a1cf71","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"noauth","isInherited":false},"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/health","description":"<p>Same as <strong>Health</strong> but without the <code>Authorization</code> header. Every <code>/v1</code> endpoint returns <strong>401</strong> with <code>WWW-Authenticate: Bearer</code> when the token is missing, malformed or wrong.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS -i \"$BASE_URL/v1/health\"\n</code></pre>\n","urlObject":{"path":["v1","health"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"671ada4f-febd-4e6e-98f2-a1bcf71e79e8","name":"401 Unauthorized","originalRequest":{"auth":{"type":"noauth"},"method":"GET","header":[],"url":"https://api.agrimbaza.awesomity.rw/v1/health","description":"Same as **Health** but without the `Authorization` header. Every `/v1` endpoint returns **401** with `WWW-Authenticate: Bearer` when the token is missing, malformed or wrong.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS -i \"$BASE_URL/v1/health\"\n```"},"status":"Unauthorized","code":401,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"},{"key":"WWW-Authenticate","value":"Bearer"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"unauthorized\",\n    \"message\": \"Missing or invalid bearer token.\"\n  }\n}"}],"_postman_id":"b478749a-94b8-461d-bb7a-872b33a1cf71"}],"id":"79def277-2a9b-41b4-8423-4439dbd7bce7","description":"<p>Readiness, model information and authentication. The evaluator calls <strong>Health</strong> before each run; the run does not start unless it returns 200.</p>\n","_postman_id":"79def277-2a9b-41b4-8423-4439dbd7bce7","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}}},{"name":"2. LLM — Simple QA and Reasoning","item":[{"name":"Simple QA — English","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const MAX_JSON = 5 * 1024 * 1024;","pm.test(\"Body is at most 5 MiB\", () => pm.expect(pm.response.size().body).to.be.at.most(MAX_JSON));","let body;","pm.test(\"Body is a JSON object\", () => {","    body = pm.response.json();","    pm.expect(body).to.be.an(\"object\").and.not.an(\"array\");","});","","pm.test(\"`answer` is present and is a string (may be empty)\", () => {","    pm.expect(body).to.have.property(\"answer\");","    pm.expect(body.answer).to.be.a(\"string\");","});","pm.test(\"`thinking`, if present, is a string or null\", () => {","    if (\"thinking\" in body) pm.expect(body.thinking === null || typeof body.thinking === \"string\").to.be.true;","});","pm.test(\"`thinking` is null for simple_qa\", () => pm.expect(body.thinking).to.equal(null));","pm.test(\"Completed within the 60 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(60000));"],"id":"1696b99f-eabb-4d32-92f8-fb0226fc5755"}}],"id":"ecf92976-f6bc-49ff-8e9a-feb38b0b71e7","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_simple_en\",\n  \"language\": \"en\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Which nutrient deficiency causes yellowing of older maize leaves?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"<p>Sends one <code>simple_qa</code> question in <code>en</code>. Only <code>answer</code> is used for Simple QA. Evaluation time limit: <strong>60 s</strong>.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"llm_demo_simple_en\",\n  \"language\": \"en\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Which nutrient deficiency causes yellowing of older maize leaves?\"\n}\nJSON\n</code></pre>\n<p>The saved example response is illustrative; real answers come from the configured model.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","llm"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"ffcbf32c-4bee-4f88-8633-14b9e3a90308","name":"200 OK (illustrative)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_simple_en\",\n  \"language\": \"en\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Which nutrient deficiency causes yellowing of older maize leaves?\"\n}"},"url":"https://api.agrimbaza.awesomity.rw/v1/llm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"answer\": \"Nitrogen deficiency. It usually starts as yellowing along the midrib of the older, lower leaves.\",\n  \"thinking\": null\n}"}],"_postman_id":"ecf92976-f6bc-49ff-8e9a-feb38b0b71e7"},{"name":"Simple QA — Kinyarwanda","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const MAX_JSON = 5 * 1024 * 1024;","pm.test(\"Body is at most 5 MiB\", () => pm.expect(pm.response.size().body).to.be.at.most(MAX_JSON));","let body;","pm.test(\"Body is a JSON object\", () => {","    body = pm.response.json();","    pm.expect(body).to.be.an(\"object\").and.not.an(\"array\");","});","","pm.test(\"`answer` is present and is a string (may be empty)\", () => {","    pm.expect(body).to.have.property(\"answer\");","    pm.expect(body.answer).to.be.a(\"string\");","});","pm.test(\"`thinking`, if present, is a string or null\", () => {","    if (\"thinking\" in body) pm.expect(body.thinking === null || typeof body.thinking === \"string\").to.be.true;","});","pm.test(\"`thinking` is null for simple_qa\", () => pm.expect(body.thinking).to.equal(null));","pm.test(\"Completed within the 60 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(60000));"],"id":"c67a2b66-4d00-4d53-bd84-d54035b01a99"}}],"id":"8d5316f3-a8fa-4c88-9426-89f7308eda82","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_simple_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Ni ibihe bihingwa byera cyane mu Rwanda?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"<p>Sends one <code>simple_qa</code> question in <code>rw</code>. Only <code>answer</code> is used for Simple QA. Evaluation time limit: <strong>60 s</strong>.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"llm_demo_simple_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Ni ibihe bihingwa byera cyane mu Rwanda?\"\n}\nJSON\n</code></pre>\n<p>The saved example response is illustrative; real answers come from the configured model.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","llm"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"9528567b-09a0-4b5d-a75e-493f31f9dc47","name":"200 OK (illustrative)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_simple_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Ni ibihe bihingwa byera cyane mu Rwanda?\"\n}"},"url":"https://api.agrimbaza.awesomity.rw/v1/llm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"answer\": \"(illustrative) Ibishyimbo, ibigori, ibirayi, imyumbati n'urutoki.\",\n  \"thinking\": null\n}"}],"_postman_id":"8d5316f3-a8fa-4c88-9426-89f7308eda82"},{"name":"Reasoning — English","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const MAX_JSON = 5 * 1024 * 1024;","pm.test(\"Body is at most 5 MiB\", () => pm.expect(pm.response.size().body).to.be.at.most(MAX_JSON));","let body;","pm.test(\"Body is a JSON object\", () => {","    body = pm.response.json();","    pm.expect(body).to.be.an(\"object\").and.not.an(\"array\");","});","","pm.test(\"`answer` is present and is a string (may be empty)\", () => {","    pm.expect(body).to.have.property(\"answer\");","    pm.expect(body.answer).to.be.a(\"string\");","});","pm.test(\"`thinking`, if present, is a string or null\", () => {","    if (\"thinking\" in body) pm.expect(body.thinking === null || typeof body.thinking === \"string\").to.be.true;","});","pm.test(\"`thinking` is a non-empty string for reasoning\", () => pm.expect(body.thinking).to.be.a(\"string\").and.not.empty);","pm.test(\"Completed within the 180 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(180000));"],"id":"d82b513f-7a8c-40e9-a959-fd7ea68ff062"}}],"id":"8bdc8d2d-9c93-4c77-8296-80fa6f6ceaea","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_reasoning_en\",\n  \"language\": \"en\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"A farmer plants maize at 75 cm between rows and 25 cm between plants, one seed per hole. How many plants are there per hectare?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"<p>Sends one <code>reasoning</code> question in <code>en</code>. Both parts are shown to the rating panel: steps in <code>thinking</code>, conclusion in <code>answer</code>. Evaluation time limit: <strong>180 s</strong>.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"llm_demo_reasoning_en\",\n  \"language\": \"en\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"A farmer plants maize at 75 cm between rows and 25 cm between plants, one seed per hole. How many plants are there per hectare?\"\n}\nJSON\n</code></pre>\n<p>The saved example response is illustrative; real answers come from the configured model.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","llm"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"505b200f-1575-4b2d-a5db-8bd7ccb97083","name":"200 OK (illustrative)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_reasoning_en\",\n  \"language\": \"en\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"A farmer plants maize at 75 cm between rows and 25 cm between plants, one seed per hole. How many plants are there per hectare?\"\n}"},"url":"https://api.agrimbaza.awesomity.rw/v1/llm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"answer\": \"About 53,333 plants per hectare.\",\n  \"thinking\": \"One hectare is 10,000 m². Each plant occupies 0.75 m × 0.25 m = 0.1875 m². 10,000 / 0.1875 ≈ 53,333 plants.\"\n}"},{"id":"a5f2fa2c-8461-4f6f-9ec5-33004429d829","name":"504 Provider deadline exceeded","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_reasoning_en\",\n  \"language\": \"en\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"A farmer plants maize at 75 cm between rows and 25 cm between plants, one seed per hole. How many plants are there per hectare?\"\n}"},"url":"https://api.agrimbaza.awesomity.rw/v1/llm"},"status":"Gateway Timeout","code":504,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"upstream_timeout\",\n    \"message\": \"The reasoning provider did not answer within 175 s.\"\n  }\n}"}],"_postman_id":"8bdc8d2d-9c93-4c77-8296-80fa6f6ceaea"},{"name":"Reasoning — Kinyarwanda","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const MAX_JSON = 5 * 1024 * 1024;","pm.test(\"Body is at most 5 MiB\", () => pm.expect(pm.response.size().body).to.be.at.most(MAX_JSON));","let body;","pm.test(\"Body is a JSON object\", () => {","    body = pm.response.json();","    pm.expect(body).to.be.an(\"object\").and.not.an(\"array\");","});","","pm.test(\"`answer` is present and is a string (may be empty)\", () => {","    pm.expect(body).to.have.property(\"answer\");","    pm.expect(body.answer).to.be.a(\"string\");","});","pm.test(\"`thinking`, if present, is a string or null\", () => {","    if (\"thinking\" in body) pm.expect(body.thinking === null || typeof body.thinking === \"string\").to.be.true;","});","pm.test(\"`thinking` is a non-empty string for reasoning\", () => pm.expect(body.thinking).to.be.a(\"string\").and.not.empty);","pm.test(\"Completed within the 180 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(180000));"],"id":"110ce0e2-a946-4a38-a014-dfde8aa1b201"}}],"id":"ce44d1ca-ded7-4875-aee6-dea5c8ea7028","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_reasoning_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"Umuhinzi afite hegitari 2. Asarura toni 3 z'ibigori kuri hegitari. Yasaruye toni zingahe zose?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"<p>Sends one <code>reasoning</code> question in <code>rw</code>. Both parts are shown to the rating panel: steps in <code>thinking</code>, conclusion in <code>answer</code>. Evaluation time limit: <strong>180 s</strong>.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"llm_demo_reasoning_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"Umuhinzi afite hegitari 2. Asarura toni 3 z'ibigori kuri hegitari. Yasaruye toni zingahe zose?\"\n}\nJSON\n</code></pre>\n<p>The saved example response is illustrative; real answers come from the configured model.</p>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","llm"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"4d653d9b-dad3-4660-99c2-17b732318e02","name":"200 OK (illustrative)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_reasoning_rw\",\n  \"language\": \"rw\",\n  \"question_type\": \"reasoning\",\n  \"question\": \"Umuhinzi afite hegitari 2. Asarura toni 3 z'ibigori kuri hegitari. Yasaruye toni zingahe zose?\"\n}"},"url":"https://api.agrimbaza.awesomity.rw/v1/llm"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"answer\": \"(illustrative) Toni 6.\",\n  \"thinking\": \"(illustrative) Hegitari 2 × toni 3 kuri hegitari = toni 6.\"\n}"}],"_postman_id":"ce44d1ca-ded7-4875-aee6-dea5c8ea7028"},{"name":"Validation — unsupported language (expect 422)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 422\", () => pm.response.to.have.status(422));","pm.test(\"Error body has code and message\", () => {","    const e = pm.response.json().error;","    pm.expect(e.code).to.equal(\"invalid_request\");","    pm.expect(e.message).to.be.a(\"string\");","});"],"id":"da56211c-a7b9-42d3-974f-bf04dbd03cd5"}}],"id":"a7732800-036d-4730-841b-705d218acaa7","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_invalid\",\n  \"language\": \"fr\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Bonjour ?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"<p>Shows the error format for an invalid request. <code>language</code> must be <code>rw</code> or <code>en</code>, so this returns <strong>422</strong>, which the evaluator treats as a final failure (not retried).</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"llm_demo_invalid\",\n  \"language\": \"fr\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Bonjour ?\"\n}\nJSON\n</code></pre>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","llm"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"dc852e88-d638-40d8-ba64-66ed0d354669","name":"422 Invalid request","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"llm_demo_invalid\",\n  \"language\": \"fr\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Bonjour ?\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/llm","description":"Shows the error format for an invalid request. `language` must be `rw` or `en`, so this returns **422**, which the evaluator treats as a final failure (not retried).\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/llm\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  --data-binary @- <<'JSON'\n{\n  \"id\": \"llm_demo_invalid\",\n  \"language\": \"fr\",\n  \"question_type\": \"simple_qa\",\n  \"question\": \"Bonjour ?\"\n}\nJSON\n```"},"status":"Unprocessable Entity","code":422,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"language: Input should be 'rw' or 'en'\"\n  }\n}"}],"_postman_id":"a7732800-036d-4730-841b-705d218acaa7"}],"id":"3539fdbf-12c4-43d9-9719-3d9127972345","description":"<p><code>POST /v1/llm</code> answers one question per request.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>id</code></td>\n<td>The question's id. Used only for log correlation.</td>\n</tr>\n<tr>\n<td><code>language</code></td>\n<td><code>rw</code> (Kinyarwanda) or <code>en</code> (English).</td>\n</tr>\n<tr>\n<td><code>question_type</code></td>\n<td><code>simple_qa</code> or <code>reasoning</code>.</td>\n</tr>\n<tr>\n<td><code>question</code></td>\n<td>The question text, sent to the model as-is (max 100,000 characters).</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Response</strong> (<code>application/json</code>):</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>answer</code></td>\n<td>string</td>\n<td>Always present. May be <code>\"\"</code>. For <code>reasoning</code>, the final conclusion.</td>\n</tr>\n<tr>\n<td><code>thinking</code></td>\n<td>string or null</td>\n<td><code>reasoning</code>: the explanation of the steps, written by the model for the rating panel. <code>simple_qa</code>: always <code>null</code>.</td>\n</tr>\n</tbody>\n</table>\n</div><p>Time limits: <strong>60 s</strong> for <code>simple_qa</code>, <strong>180 s</strong> for <code>reasoning</code>. The response is never larger than 5 MiB.</p>\n<p><strong>Errors</strong> are JSON: <code>{\"error\": {\"code\": \"...\", \"message\": \"...\"}}</code>. Provider failures return 429, 502, 503 or 504 (retryable; 429 and 503 include <code>Retry-After</code> in seconds). Invalid requests return 401, 404 or 422 (not retryable).</p>\n","_postman_id":"3539fdbf-12c4-43d9-9719-3d9127972345","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}}},{"name":"3. ASR — speech recognition","item":[{"name":"Transcribe a WAV clip","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","const MAX_JSON = 5 * 1024 * 1024;","pm.test(\"Body is at most 5 MiB\", () => pm.expect(pm.response.size().body).to.be.at.most(MAX_JSON));","let body;","pm.test(\"Body is a JSON object\", () => {","    body = pm.response.json();","    pm.expect(body).to.be.an(\"object\").and.not.an(\"array\");","});","","pm.test(\"`transcript` is present and is a string (may be empty)\", () => {","    pm.expect(body).to.have.property(\"transcript\");","    pm.expect(body.transcript).to.be.a(\"string\");","});","","pm.test(\"Completed within the 60 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(60000));"],"id":"ba6671f5-8adc-4a72-9d4b-cb572bf62d7e"}}],"id":"c7710570-93cc-4fa6-86bc-e83e299b09a9","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"audio","type":"file","description":"<p>The clip's WAV bytes, named <code>&lt;id&gt;.wav</code>. Select a file before sending.</p>\n","value":null},{"key":"id","value":"asr_demo_001","type":"text","description":"<p>The clip's id.</p>\n"},{"key":"language","value":"rw","type":"text","description":"<p>Always <code>rw</code>.</p>\n"}]},"url":"https://api.agrimbaza.awesomity.rw/v1/asr","description":"<p><strong>Before sending:</strong> open the <strong>Body</strong> tab and choose a WAV file for the <code>audio</code> field (Postman does not store files in a shared collection).</p>\n<p>Request is <code>multipart/form-data</code> with three parts:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Part</th>\n<th>Content</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>audio</code></td>\n<td>The clip's WAV bytes, filename <code>&lt;id&gt;.wav</code>, content type <code>audio/wav</code>. Up to 25 MiB.</td>\n</tr>\n<tr>\n<td><code>id</code></td>\n<td>The clip's id.</td>\n</tr>\n<tr>\n<td><code>language</code></td>\n<td><code>rw</code></td>\n</tr>\n</tbody>\n</table>\n</div><p>The service checks only that the file has a RIFF/WAVE header and passes the audio to the ASR model. The response is <code>{\"transcript\": \"...\"}</code>: a string, possibly empty, returned exactly as the model produced it (no normalisation). Evaluation time limit: <strong>60 s</strong>.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/asr\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -F \"audio=@asr_demo_001.wav;type=audio/wav\" \\\n  -F \"id=asr_demo_001\" \\\n  -F \"language=rw\"\n</code></pre>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","asr"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"7c0d3c4f-8f50-4c73-963f-c4dbc5bec431","name":"200 OK (illustrative)","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"audio","type":"file","description":"The clip's WAV bytes, named `<id>.wav`. Select a file before sending.","value":null},{"key":"id","value":"asr_demo_001","type":"text","description":"The clip's id."},{"key":"language","value":"rw","type":"text","description":"Always `rw`."}]},"url":"https://api.agrimbaza.awesomity.rw/v1/asr","description":"**Before sending:** open the **Body** tab and choose a WAV file for the `audio` field (Postman does not store files in a shared collection).\n\nRequest is `multipart/form-data` with three parts:\n\n| Part | Content |\n|---|---|\n| `audio` | The clip's WAV bytes, filename `<id>.wav`, content type `audio/wav`. Up to 25 MiB. |\n| `id` | The clip's id. |\n| `language` | `rw` |\n\nThe service checks only that the file has a RIFF/WAVE header and passes the audio to the ASR model. The response is `{\"transcript\": \"...\"}`: a string, possibly empty, returned exactly as the model produced it (no normalisation). Evaluation time limit: **60 s**.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/asr\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -F \"audio=@asr_demo_001.wav;type=audio/wav\" \\\n  -F \"id=asr_demo_001\" \\\n  -F \"language=rw\"\n```"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"transcript\": \"Ibigori bikenera amazi ahagije mu gihe cyo kumera.\"\n}"},{"id":"8cde54a3-eb1d-428f-9186-dfc11bfd7079","name":"422 Not a WAV file","originalRequest":{"method":"POST","header":[],"body":{"mode":"formdata","formdata":[{"key":"audio","type":"file","description":"The clip's WAV bytes, named `<id>.wav`. Select a file before sending.","value":null},{"key":"id","value":"asr_demo_001","type":"text","description":"The clip's id."},{"key":"language","value":"rw","type":"text","description":"Always `rw`."}]},"url":"https://api.agrimbaza.awesomity.rw/v1/asr","description":"**Before sending:** open the **Body** tab and choose a WAV file for the `audio` field (Postman does not store files in a shared collection).\n\nRequest is `multipart/form-data` with three parts:\n\n| Part | Content |\n|---|---|\n| `audio` | The clip's WAV bytes, filename `<id>.wav`, content type `audio/wav`. Up to 25 MiB. |\n| `id` | The clip's id. |\n| `language` | `rw` |\n\nThe service checks only that the file has a RIFF/WAVE header and passes the audio to the ASR model. The response is `{\"transcript\": \"...\"}`: a string, possibly empty, returned exactly as the model produced it (no normalisation). Evaluation time limit: **60 s**.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/asr\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -F \"audio=@asr_demo_001.wav;type=audio/wav\" \\\n  -F \"id=asr_demo_001\" \\\n  -F \"language=rw\"\n```"},"status":"Unprocessable Entity","code":422,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"invalid_audio\",\n    \"message\": \"The uploaded file is not a WAV file (missing RIFF/WAVE header).\"\n  }\n}"}],"_postman_id":"c7710570-93cc-4fa6-86bc-e83e299b09a9"}],"id":"8d48547e-dbeb-4969-8cb6-13502c1f353a","description":"<p><code>POST /v1/asr</code> transcribes one Kinyarwanda WAV clip. Response: <code>{\"transcript\": \"...\"}</code>.</p>\n<p><strong>Errors</strong> are JSON: <code>{\"error\": {\"code\": \"...\", \"message\": \"...\"}}</code>. Provider failures return 429, 502, 503 or 504 (retryable; 429 and 503 include <code>Retry-After</code> in seconds). Invalid requests return 401, 404 or 422 (not retryable).</p>\n","_postman_id":"8d48547e-dbeb-4969-8cb6-13502c1f353a","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}}},{"name":"4. TTS — speech synthesis","item":[{"name":"Synthesise speech (WAV)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status is 200\", () => pm.response.to.have.status(200));","pm.test(\"Content-Type is audio/wav\", () =>","    pm.expect(pm.response.headers.get(\"Content-Type\").split(\";\")[0].trim().toLowerCase()).to.equal(\"audio/wav\"));","","const wav = pm.response.stream;  // raw response bytes (Buffer)","pm.test(\"Starts with RIFF and bytes 8-11 are WAVE\", () => {","    pm.expect(wav.toString(\"ascii\", 0, 4)).to.equal(\"RIFF\");","    pm.expect(wav.toString(\"ascii\", 8, 12)).to.equal(\"WAVE\");","});","pm.test(\"At most 30 MiB\", () => pm.expect(wav.length).to.be.at.most(30 * 1024 * 1024));","pm.test(\"RIFF size in the header matches the body (no streaming placeholder)\", () => {","    const riff = wav.readUInt32LE(4);","    pm.expect(riff).to.not.be.oneOf([0, 0xFFFFFFFF]);","    pm.expect(riff + 8).to.equal(wav.length);","});","","// Duration as the evaluator reads it: data chunk size / byte rate from the fmt chunk.","let pos = 12, byteRate = 0, dataSize = null;","while (pos + 8 <= wav.length) {","    const id = wav.toString(\"ascii\", pos, pos + 4), size = wav.readUInt32LE(pos + 4);","    if (id === \"fmt \") byteRate = wav.readUInt32LE(pos + 16);","    if (id === \"data\") { dataSize = size; break; }","    pos += 8 + size + (size & 1);","}","const seconds = byteRate && dataSize !== null ? dataSize / byteRate : 0;","pm.test(`Duration is more than 0 s and at most 30 s (got ${seconds.toFixed(2)} s)`, () => {","    pm.expect(seconds).to.be.above(0);","    pm.expect(seconds).to.be.at.most(30);","});","","pm.test(\"Completed within the 60 s evaluation limit\", () =>","    pm.expect(pm.response.responseTime).to.be.below(60000));"],"id":"50f9d71a-9c5a-4bb9-be19-cc26ad9d2895"}}],"id":"7610041f-2993-4c18-a434-76e4fa398b0d","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/tts","description":"<p>Returns the spoken <code>text</code> as <strong>WAV bytes</strong> in the response body with <code>Content-Type: audio/wav</code> (not JSON, not base64, not a link). <code>language</code> is always <code>rw</code>.</p>\n<p>Before returning 200 the service checks that the clip starts with <code>RIFF</code>, has <code>WAVE</code> at bytes 8–11, has header sizes that match the file, is at most 30 MiB and lasts more than 0 s and at most 30 s. The full clip is generated before the first byte is sent, so time to first byte is close to total response time. Evaluation time limit: <strong>60 s</strong>.</p>\n<p><strong>To listen in Postman:</strong> use the arrow next to <strong>Send</strong> → <strong>Send and Download</strong>, then open the saved file.</p>\n<p><strong>curl</strong> (after setting <code>BASE_URL</code> and <code>TOKEN</code>, see the collection overview):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-bash\">curl -sS \"$BASE_URL/v1/tts\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -o tts_demo_001.wav \\\n  --data-binary @- &lt;&lt;'JSON'\n{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}\nJSON\n</code></pre>\n","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}},"urlObject":{"path":["v1","tts"],"host":["https://api.agrimbaza.awesomity.rw"],"query":[],"variable":[]}},"response":[{"id":"bf72ad2c-6198-4fe7-b9ab-cb20d0172bb0","name":"200 OK (binary WAV, not shown)","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/tts","description":"Returns the spoken `text` as **WAV bytes** in the response body with `Content-Type: audio/wav` (not JSON, not base64, not a link). `language` is always `rw`.\n\nBefore returning 200 the service checks that the clip starts with `RIFF`, has `WAVE` at bytes 8–11, has header sizes that match the file, is at most 30 MiB and lasts more than 0 s and at most 30 s. The full clip is generated before the first byte is sent, so time to first byte is close to total response time. Evaluation time limit: **60 s**.\n\n**To listen in Postman:** use the arrow next to **Send** → **Send and Download**, then open the saved file.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/tts\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -o tts_demo_001.wav \\\n  --data-binary @- <<'JSON'\n{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}\nJSON\n```"},"status":"OK","code":200,"_postman_previewlanguage":"text","header":[{"key":"Content-Type","value":"audio/wav"}],"cookie":[],"responseTime":null,"body":"<binary WAV audio: RIFF/WAVE, 24 kHz, 16-bit, mono>"},{"id":"b991d82f-82ee-4172-a02d-e5a4dcda01a9","name":"502 Generated audio failed validation","originalRequest":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"body":{"mode":"raw","raw":"{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}","options":{"raw":{"language":"json"}}},"url":"https://api.agrimbaza.awesomity.rw/v1/tts","description":"Returns the spoken `text` as **WAV bytes** in the response body with `Content-Type: audio/wav` (not JSON, not base64, not a link). `language` is always `rw`.\n\nBefore returning 200 the service checks that the clip starts with `RIFF`, has `WAVE` at bytes 8–11, has header sizes that match the file, is at most 30 MiB and lasts more than 0 s and at most 30 s. The full clip is generated before the first byte is sent, so time to first byte is close to total response time. Evaluation time limit: **60 s**.\n\n**To listen in Postman:** use the arrow next to **Send** → **Send and Download**, then open the saved file.\n\n**curl** (after setting `BASE_URL` and `TOKEN`, see the collection overview):\n\n```bash\ncurl -sS \"$BASE_URL/v1/tts\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -o tts_demo_001.wav \\\n  --data-binary @- <<'JSON'\n{\n  \"id\": \"tts_demo_001\",\n  \"language\": \"rw\",\n  \"text\": \"Muraho, amakuru yanyu? Uyu munsi turavuga ku buhinzi bw'ibigori.\"\n}\nJSON\n```"},"status":"Bad Gateway","code":502,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"error\": {\n    \"code\": \"audio_duration_out_of_range\",\n    \"message\": \"The generated audio is 31.20 s long; it must be more than 0 s and at most 30 s.\"\n  }\n}"}],"_postman_id":"7610041f-2993-4c18-a434-76e4fa398b0d"}],"id":"ba695208-2877-46be-be69-c5a4ccd605ae","description":"<p><code>POST /v1/tts</code> turns Kinyarwanda text into a WAV clip returned as raw bytes.</p>\n<p><strong>Errors</strong> are JSON: <code>{\"error\": {\"code\": \"...\", \"message\": \"...\"}}</code>. Provider failures return 429, 502, 503 or 504 (retryable; 429 and 503 include <code>Retry-After</code> in seconds). Invalid requests return 401, 404 or 422 (not retryable).</p>\n","_postman_id":"ba695208-2877-46be-be69-c5a4ccd605ae","auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]},"isInherited":true,"source":{"_postman_id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","id":"f52c8c09-c572-40d4-84f8-e49a8fddc835","name":"AgriMbaza QA Evaluation API","type":"collection"}}}],"auth":{"type":"bearer","bearer":{"basicConfig":[{"key":"token","value":""}]}},"variable":[{"key":"baseUrl","value":"https://api.agrimbaza.awesomity.rw","description":"Registered base URL, no trailing slash."},{"key":"token","value":"","description":"Bearer token. Set it as the Current value; never share it inside the collection."}]}