{"course":{"slug":"building-with-ai","title":"Building with AI","url":"https://addaly.com/learn/building-with-ai","level":"intermediate","track":"build"},"lesson":{"slug":"first-api-call","title":"The call underneath everything","url":"https://addaly.com/learn/building-with-ai/first-api-call","position":1,"of":80,"minutes":6,"answer":"The API is stateless: every turn you pay to resend the entire conversation.","keyIdea":"The API is stateless: every turn you pay to resend the entire conversation."},"body":{"format":"markdown","text":"## It is one HTTP request\n\nEverything you build sits on a single POST. There is no session, no connection held open, no magic. JSON goes out, JSON comes back.\n\n```bash\ncurl https://api.anthropic.com/v1/messages \\\n  -H \"x-api-key: $ANTHROPIC_API_KEY\" \\\n  -H \"anthropic-version: 2023-06-01\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\n    \"model\": \"claude-sonnet-4-5\",\n    \"max_tokens\": 300,\n    \"messages\": [{\"role\": \"user\", \"content\": \"Summarise this email in one line: ...\"}]\n  }'\n```\n\nThe SDK is a thin wrapper over exactly that request.\n\n```python\nimport os\nfrom anthropic import Anthropic\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nr = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=300,\n    messages=[{\"role\": \"user\", \"content\": f\"Summarise this email in one line:\\n\\n{email}\"}],\n)\nprint(r.content[0].text)\nprint(r.usage)        # Usage(input_tokens=812, output_tokens=41)\nprint(r.stop_reason)  # 'end_turn' — or 'max_tokens' if it was cut off\n```\n\nOther providers use different field names and the same shape: a model id, a list of messages, a cap on output length. Learn one properly and the rest take an hour.\n\n## The key is a password with a bill attached\n\nAn API key is a credential that spends money. Three rules, and people break all three:\n\n- Read it from an environment variable or a secret store. Never a literal in the source.\n- Never ship it to a browser or a mobile app. Anyone can open devtools and read it. If your frontend needs the model, put your own server in between.\n- Assume it will leak eventually. Know how to rotate it, and set a spend limit in the provider console today, not after the incident.\n\n## The model has no memory\n\nThis surprises people more than anything else. The API is stateless. It does not remember your last call. A conversation exists only because you resend the whole thing:\n\n```python\nmessages = []\nmessages.append({\"role\": \"user\", \"content\": \"My order is late.\"})\nreply = call(messages)\nmessages.append({\"role\": \"assistant\", \"content\": reply})\nmessages.append({\"role\": \"user\", \"content\": \"How late?\"})\nreply = call(messages)   # the whole transcript goes over the wire again\n```\n\nSo turn 40 of a chat costs far more than turn 2. That is not a bug, it is the billing model, and it is why long chats need trimming or summarising.\n\n## What you actually pay for\n\nYou pay per token, separately for input and output, with output usually several times the price of input. A token is roughly three quarters of an English word.\n\nOne detail that matters if your users are not writing English: tokenizers are trained mostly on English text, so the same sentence in Hindi, Telugu, Amharic or Thai can cost two to five times the tokens of its English translation. A cost model built on English test data will be wrong in production in Chennai or Addis Ababa.\n\nDo the arithmetic before you build. Say a support-reply feature sends 1,200 input tokens and gets 300 output tokens back, 5,000 times a day. That is 6M input and 1.5M output tokens daily. At a rate of $3 per million input and $15 per million output, that is $18 + $22.50 = $40.50 a day, about $1,215 a month. Now you know whether this feature is worth building, and you knew before writing it.\n\n## Two settings to always pass\n\n`max_tokens` caps the reply. Without a sane value you can pay for a 4,000-token essay when you wanted a label. A timeout caps the wait. Model calls can take 30 seconds; a request with no timeout will hold a worker until something else gives up.\n\n```python\nr = client.messages.create(model=MODEL, max_tokens=64, timeout=20.0, messages=msgs)\n```\n\nThat is the whole foundation. Everything else in this course is what you put in `messages`, and what you do with what comes back.\n\n> Figure: What one turn of a chat costs to send. Line chart of Input tokens billed on that turn against Turn number: Whole history resent (2, 240) (10, 1680) (20, 3480) (30, 5280) (40, 7080); Trimmed to the last ten exchanges (2, 240) (10, 1680) (20, 1860) (30, 1860) (40, 1860). A 60-token question and a 120-token reply each turn. Because the API is stateless, turn 40 resends everything before it. Cost per turn grows in a straight line and the cost of the whole conversation grows with its square, which is why a cap is not a nicety."},"check":{"question":"A chat feature has been live for a month. Analytics show that the 40th message in a long thread costs several times what the 2nd message cost, for the same model and similar reply lengths. What is going on?","options":[{"text":"The provider keeps your thread on its servers between calls and bills for holding that context.","correct":false,"tempting":"SDKs with conversation helpers and thread ids make it look as though the server is storing your history for you."},{"text":"Each call resends the full transcript, so the input token count grows with every turn.","correct":true},{"text":"Later replies in a thread are longer, and output tokens are priced higher than input tokens.","correct":false,"tempting":"Output really is the expensive half, so blaming it feels like the safe bet."},{"text":"The model internally re-summarises the earlier turns before answering, and you are billed for that hidden work.","correct":false,"tempting":"Hidden reasoning tokens do exist on some models, so an invisible extra step sounds plausible."}]},"prev":null,"next":{"slug":"system-prompts-and-turns","title":"System prompts, user turns, and who the model listens to","url":"https://addaly.com/learn/building-with-ai/system-prompts-and-turns"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The call underneath everything', in Building with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-with-ai/first-api-call (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The call underneath everything","courseTitle":"Building with AI","module":{"index":1,"slug":"the-call-underneath-everything","title":"The call underneath everything"},"answer":null,"keyIdea":"The API is stateless: every turn you pay to resend the entire conversation.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure 1.1","figure":{"type":"line","title":"What one turn of a chat costs to send","series":[{"label":"Whole history resent","points":[[2,240],[10,1680],[20,3480],[30,5280],[40,7080]]},{"label":"Trimmed to the last ten exchanges","points":[[2,240],[10,1680],[20,1860],[30,1860],[40,1860]]}],"xLabel":"Turn number","yLabel":"Input tokens billed on that turn","caption":"A 60-token question and a 120-token reply each turn. Because the API is stateless, turn 40 resends everything before it. Cost per turn grows in a straight line and the cost of the whole conversation grows with its square, which is why a cap is not a nicety."},"alt":"Figure: What one turn of a chat costs to send. Line chart of Input tokens billed on that turn against Turn number: Whole history resent (2, 240) (10, 1680) (20, 3480) (30, 5280) (40, 7080); Trimmed to the last ten exchanges (2, 240) (10, 1680) (20, 1860) (30, 1860) (40, 1860). A 60-token question and a 120-token reply each turn. Because the API is stateless, turn 40 resends everything before it. Cost per turn grows in a straight line and the cost of the whole conversation grows with its square, which is why a cap is not a nicety."}},"blocks":[{"t":"h2","id":"it-is-one-http-request","runs":[{"text":"It is one HTTP request"}]},{"t":"p","runs":[{"text":"Everything you build sits on a single POST. There is no session, no connection held open, no magic. JSON goes out, JSON comes back."}]},{"t":"code","lang":"bash","label":"Bash","text":"curl https://api.anthropic.com/v1/messages \\\n  -H \"x-api-key: $ANTHROPIC_API_KEY\" \\\n  -H \"anthropic-version: 2023-06-01\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\n    \"model\": \"claude-sonnet-4-5\",\n    \"max_tokens\": 300,\n    \"messages\": [{\"role\": \"user\", \"content\": \"Summarise this email in one line: ...\"}]\n  }'"},{"t":"p","runs":[{"text":"The SDK is a thin wrapper over exactly that request."}]},{"t":"code","lang":"python","label":"Python","text":"import os\nfrom anthropic import Anthropic\n\nclient = Anthropic(api_key=os.environ[\"ANTHROPIC_API_KEY\"])\n\nr = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    max_tokens=300,\n    messages=[{\"role\": \"user\", \"content\": f\"Summarise this email in one line:\\n\\n{email}\"}],\n)\nprint(r.content[0].text)\nprint(r.usage)        # Usage(input_tokens=812, output_tokens=41)\nprint(r.stop_reason)  # 'end_turn' — or 'max_tokens' if it was cut off"},{"t":"p","runs":[{"text":"Other providers use different field names and the same shape: a model id, a list of messages, a cap on output length. Learn one properly and the rest take an hour."}]},{"t":"h2","id":"the-key-is-a-password-with-a-bill-attached","runs":[{"text":"The key is a password with a bill attached"}]},{"t":"p","runs":[{"text":"An API key is a credential that spends money. Three rules, and people break all three:"}]},{"t":"ul","items":[[{"text":"Read it from an environment variable or a secret store. Never a literal in the source."}],[{"text":"Never ship it to a browser or a mobile app. Anyone can open devtools and read it. If your frontend needs the model, put your own server in between."}],[{"text":"Assume it will leak eventually. Know how to rotate it, and set a spend limit in the provider console today, not after the incident."}]]},{"t":"h2","id":"the-model-has-no-memory","runs":[{"text":"The model has no memory"}]},{"t":"p","runs":[{"text":"This surprises people more than anything else. The API is stateless. It does not remember your last call. A conversation exists only because you resend the whole thing:"}]},{"t":"code","lang":"python","label":"Python","text":"messages = []\nmessages.append({\"role\": \"user\", \"content\": \"My order is late.\"})\nreply = call(messages)\nmessages.append({\"role\": \"assistant\", \"content\": reply})\nmessages.append({\"role\": \"user\", \"content\": \"How late?\"})\nreply = call(messages)   # the whole transcript goes over the wire again"},{"t":"p","runs":[{"text":"So turn 40 of a chat costs far more than turn 2. That is not a bug, it is the billing model, and it is why long chats need trimming or summarising."}]},{"t":"h2","id":"what-you-actually-pay-for","runs":[{"text":"What you actually pay for"}]},{"t":"p","runs":[{"text":"You pay per token, separately for input and output, with output usually several times the price of input. A token is roughly three quarters of an English word."}]},{"t":"p","runs":[{"text":"One detail that matters if your users are not writing English: tokenizers are trained mostly on English text, so the same sentence in Hindi, Telugu, Amharic or Thai can cost two to five times the tokens of its English translation. A cost model built on English test data will be wrong in production in Chennai or Addis Ababa."}]},{"t":"p","runs":[{"text":"Do the arithmetic before you build. Say a support-reply feature sends 1,200 input tokens and gets 300 output tokens back, 5,000 times a day. That is 6M input and 1.5M output tokens daily. At a rate of $3 per million input and $15 per million output, that is $18 + $22.50 = $40.50 a day, about $1,215 a month. Now you know whether this feature is worth building, and you knew before writing it."}]},{"t":"h2","id":"two-settings-to-always-pass","runs":[{"text":"Two settings to always pass"}]},{"t":"p","runs":[{"text":"max_tokens","code":true},{"text":" caps the reply. Without a sane value you can pay for a 4,000-token essay when you wanted a label. A timeout caps the wait. Model calls can take 30 seconds; a request with no timeout will hold a worker until something else gives up."}]},{"t":"code","lang":"python","label":"Python","text":"r = client.messages.create(model=MODEL, max_tokens=64, timeout=20.0, messages=msgs)"},{"t":"p","runs":[{"text":"That is the whole foundation. Everything else in this course is what you put in "},{"text":"messages","code":true},{"text":", and what you do with what comes back."}]}],"check":{"question":[{"text":"A chat feature has been live for a month. Analytics show that the 40th message in a long thread costs several times what the 2nd message cost, for the same model and similar reply lengths. What is going on?"}],"options":[{"text":[{"text":"The provider keeps your thread on its servers between calls and bills for holding that context."}],"correct":false,"tempting":[{"text":"SDKs with conversation helpers and thread ids make it look as though the server is storing your history for you."}]},{"text":[{"text":"Each call resends the full transcript, so the input token count grows with every turn."}],"correct":true,"tempting":null},{"text":[{"text":"Later replies in a thread are longer, and output tokens are priced higher than input tokens."}],"correct":false,"tempting":[{"text":"Output really is the expensive half, so blaming it feels like the safe bet."}]},{"text":[{"text":"The model internally re-summarises the earlier turns before answering, and you are billed for that hidden work."}],"correct":false,"tempting":[{"text":"Hidden reasoning tokens do exist on some models, so an invisible extra step sounds plausible."}]}],"order":[0,2,3,1],"why":null},"prev":null,"next":{"slug":"system-prompts-and-turns","title":"System prompts, user turns, and who the model listens to","minutes":6}}}