Ready to put this into action?
Get the complete AI Integration Playbook β Practical AI implementation guide β prompt engineering, workflow automation, and ROI frameworks.
Article 108 Β· Part 11
Connect to an AI Model Through an API
Make one bounded request, inspect the response, and keep credentials and costs under deliberate control.
By Randy Salars Β· Published
On this page
- Separate the application from the credential
- Use a small, complete request program
- Read the request as a contract
- Distinguish transport success from task success
- Decide when to retry
- Estimate the cost of the whole task
- A reusable prompt
- For students: learn the boundary before paying for experiments
- Practice: inspect before connecting
Make one bounded request, inspect the response, and keep credentials and costs under deliberate control.
An application programming interface, or API, lets one program request work from another service. Instead of typing into a chat window, your program sends a request in a documented format and receives a response it must interpret.
The first successful API lesson should answer a small set of questions. Where is the request going? How is it authenticated? What input is being transmitted? What can the response contain? What happens if the service is unavailable or the answer is incomplete?
For this example, the only task is to ask for the sum of two and three. A calculator would be the sensible production tool for that arithmetic. Here, a known answer makes the network integration easy to inspect.
Separate the application from the credential
An API credential authorizes access to a service account or project. Keep it on a trusted machine or server, outside source code and browser-delivered files. The HTML application in Article 107 must not contain a real API key.
For a deployed application, the browser would normally send its request to your server. Your server would authenticate the user, enforce the application's rules and budget, and call the model provider using its protected credential. A hidden-looking JavaScript variable is still delivered to the browser.
OpenAI's API uses bearer authentication, and its API reference overview directs developers to protect credentials from client-side exposure. Before running a request, confirm the API project's billing setup, available model, and permitted data use. Do not assume access in one product establishes the same entitlement or budget in another.
Use a small, complete request program
The program below uses Python's standard library so you can see the HTTP boundary without installing an SDK. Save it as first_api_call.py.
Set two environment variables in your trusted execution environment: OPENAI_API_KEY with your own credential, and OPENAI_MODEL with a currently available model identifier that supports the Responses API and the request options shown. Obtain that identifier from your account's model access and the provider's current documentation. Configure the secret through your environment's protected settings; do not put its value into this article, a shared notebook, or source control.
Then run python3 first_api_call.py, or use your system's equivalent Python command. Running it with valid configuration makes one potentially billable request. Reading the code and running the offline parsing checks described later do not require a credential.
The endpoint and request structure follow OpenAI's developer quickstart. This example was checked against documentation on September 8, 2026; it was not authenticated against a live account during article preparation.
import json
import os
from urllib.error import HTTPError, URLError
from urllib.request import HTTPRedirectHandler, Request, build_opener
class NoRedirects(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def extract_text(response):
if not isinstance(response, dict) or response.get("status") != "completed":
raise ValueError("Response is not completed; do not use partial output")
items = response.get("output")
if not isinstance(items, list):
raise ValueError("Missing output list")
parts = []
for item in items:
if not isinstance(item, dict):
raise ValueError("Unexpected output item")
if item.get("type") != "message":
continue
content = item.get("content")
if not isinstance(content, list):
raise ValueError("Unexpected message content")
for part in content:
if not isinstance(part, dict):
raise ValueError("Unexpected content part")
if part.get("type") == "refusal":
raise ValueError("The model refused; no answer accepted")
if part.get("type") == "output_text":
if not isinstance(part.get("text"), str):
raise ValueError("Expected text")
parts.append(part["text"])
answer = "\n".join(parts).strip()
if not answer:
raise ValueError("No answer text returned")
return answer
def main():
key = os.environ.get("OPENAI_API_KEY", "").strip()
model = os.environ.get("OPENAI_MODEL", "").strip()
if not key or not model:
raise SystemExit("Set OPENAI_API_KEY and OPENAI_MODEL in your trusted environment")
payload = {"model": model, "input": "Reply with only the sum of 2 and 3.",
"max_output_tokens": 1000}
request = Request("https://api.openai.com/v1/responses",
data=json.dumps(payload).encode("utf-8"),
headers={"Authorization": f"Bearer {key}",
"Content-Type": "application/json"}, method="POST")
try:
with build_opener(NoRedirects()).open(request, timeout=20) as response:
raw = response.read(1048577)
if len(raw) > 1048576:
raise ValueError("Response exceeded this lesson's 1 MiB limit")
result = json.loads(raw)
answer = extract_text(result)
except HTTPError as exc:
raise SystemExit(f"HTTP {exc.code}; stopped without retrying") from None
except (URLError, TimeoutError, OSError):
raise SystemExit("Network failure; outcome may be unknown. No retry attempted") from None
except (ValueError, UnicodeError) as exc:
raise SystemExit(f"Response rejected: {exc}") from None
print("Answer:", answer)
if answer != "5":
raise SystemExit("Request completed, but the known-answer check failed")
print("Known-answer check passed")
if __name__ == "__main__":
main()
Read the request as a contract
The program sends one short, fixed input to one fixed HTTPS endpoint. The model identifier comes from configuration. The credential appears only in the authorization header. The output-token limit bounds generated tokens for this request; it is not a promise about a fixed word count or total invoice.
The request uses a 20-second network timeout. In this library, that timeout applies to blocking network operations; it is not a guaranteed 20-second deadline for the entire program. Production applications may need an overall deadline as well as connection and read timeouts.
The program refuses HTTP redirects rather than forwarding the request to another destination. It also bounds the response body before parsing it. These choices keep this demonstration's network behavior narrow and visible.
Nothing in the code establishes a special retention arrangement. Before sending real information, review the provider's current data controls and the terms that apply to your account. The arithmetic prompt avoids making that question part of the first experiment.
Distinguish transport success from task success
A completed HTTP exchange can still produce an unusable application result. The response may be incomplete, contain a refusal, or lack the expected answer text. The extraction function handles those states before printing an answer.
The final comparison with 5 checks the known task. It is intentionally strict. A response such as βThe answer is 5β would complete the arithmetic conceptually but fail the requested output format. You can decide whether a later application should accept that variation; the decision belongs in its specification.
Do not silently reinterpret every unexpected answer until it looks successful. If you strip arbitrary text, infer missing values, and substitute defaults without recording them, a broken integration can appear to work.
During preparation, the extraction function passed one valid synthetic response and rejected six invalid fixtures, including an incomplete response, a refusal, empty output, and a non-text answer field. These are local parser checks. They do not verify credentials, model availability, billing, network access, or a live model's response.
To repeat one check without making a request, place this short file beside first_api_call.py and run it:
from first_api_call import extract_text
fixture = {"status": "completed", "output": [
{"type": "message", "content": [{"type": "output_text", "text": "5"}]}
]}
assert extract_text(fixture) == "5"
try:
extract_text({"status": "incomplete", "output": []})
except ValueError:
print("Incomplete response correctly rejected")
else:
raise AssertionError("Incomplete response was accepted")
The if __name__ == "__main__" guard in the request file matters here: importing its parsing function does not call main and therefore does not send a request.
Decide when to retry
This first program performs no automatic retries. A network failure leaves a clear message and stops. That is a valid bounded policy for a learning exercise.
A production service may retry temporary failures, but it needs a maximum attempt count, delay policy, and overall time budget. Some errors require correcting the request or account configuration rather than repeating the same call.
If a timeout occurs after the provider accepted the request, the client may not know whether work was completed. Retrying can cause another billable generation. When a workflow also performs external actions, a duplicate attempt may have consequences beyond cost. Design action identifiers and deduplication at the relevant application boundary.
OpenAI's rate-limit guidance discusses exponential backoff with randomness and notes that unsuccessful requests can still count toward limits. A loop that retries immediately forever makes a temporary failure harder to recover from.
Estimate the cost of the whole task
Use current prices for the exact model and features you intend to use. The provider's pricing documentation is the reference to check before budgeting.
For practice, suppose a fictional service charged $2 per million input tokens and $8 per million output tokens. A request with 1,000 input tokens and 200 output tokens would cost:
- Input: 1,000 Γ· 1,000,000 Γ $2 = $0.002.
- Output: 200 Γ· 1,000,000 Γ $8 = $0.0016.
- Combined: $0.0036 for that request.
At those invented rates and token counts, 1,000 requests would cost $3.60. This is arithmetic practice, not a quoted model price. Additional tools, retries, larger prompts, and other billable features would change the total.
Measure actual usage after a small run. Repeated conversation history can make later requests larger than the first. A request limit and a spending budget solve related but different problems: one limits activity, while the other limits its financial exposure.
A reusable prompt
Explain and implement one API request for [small known task] using current official documentation. Keep the credential outside source code and client-side files. Show the exact destination, transmitted input, output limit, timeout behavior, and retry policy. Reject incomplete or unexpected responses explicitly. Provide offline fixtures for response parsing, and identify which parts require a live account to verify.
For students: learn the boundary before paying for experiments
Students can learn request structure, response parsing, and error handling with synthetic responses. An instructor can provide fixtures for success, refusal, timeout reporting, and invalid structure without requiring every student to create an account.
Where live use is permitted, use an approved account, a small budget, and public or synthetic inputs. Follow the provider's eligibility requirements and your institution's rules. A group should not circulate one person's credential in a shared notebook.
For a lab report, separate three claims: βthe parser passed local checks,β βthe endpoint accepted a request,β and βthe model met the task requirement.β Each needs different evidence. Record the model identifier and date for a live experiment, because later runs may use different available versions or settings.
Practice: inspect before connecting
Run the offline fixture first. Explain why importing the module does not send a request. Then identify the credential source, endpoint, input, token limit, and failure messages in the complete program.
If you have approved API access, make one live request and record whether both the response extraction and known-answer check succeed. If you do not, label the live portion untested and complete the local portion without inventing a result.
Completion check: You can distinguish a successful network response from a correct answer, explain the program's retry policy, and demonstrate that the credential is absent from the shared source file.
Stretch: Design a bounded retry policy for a read-only task. Specify retryable conditions, maximum attempts, total deadline, and how you will report an outcome that remains unknown after a timeout.
Get the AI Dispatch
Weekly insights on ai & technology β delivered to your inbox. No spam, unsubscribe any time.
Want to choose specific topics? Customize your interests