Written by: Doug Camplejohn, CEO & Co-Founder, Coffee | Last updated: August 6, 2026
Key Takeaways for Your Python Integration
- The 2026 Responses API replaces the legacy Chat Completions endpoint and gives Python developers cleaner syntax plus built-in state management.
- Install openai ≥ 1.40, store your key in a .env file, and use gpt-4o or later for full Responses API support.
- Single-turn calls rely on the instructions and input parameters, while multi-turn conversations pass the full history list on every request.
- Streaming with stream=True delivers tokens in real time, and token usage and billing stay the same. Use max_output_tokens to control spend.
- Structured JSON output maps directly to CRM fields, and Coffee can automatically log every interaction so you never lose context.
Set Up Your Environment and Access
- Install the SDK. Run
pip install "openai>=1.40"so your project supports the Responses API. - Store your key securely. Add
OPENAI_API_KEY=sk-...to a.envfile and load it with python-dotenv, and never hard-code credentials. - Verify Responses API access. Confirm your organization tier includes Responses API access in the OpenAI usage dashboard.
- Choose a model. Select
gpt-4oor a later checkpoint, because earlier models do not support all Responses API features. - Decide on delivery mode. Decide whether your use case needs a blocking response or token-by-token streaming before you write any code.
Make a Minimal ChatGPT Call with the Responses API
The Responses API replaces chat.completions.create with responses.create. The new shape is simpler and supports built-in state management that the old endpoint required you to wire manually.
import os from dotenv import load_dotenv from openai import OpenAI load_dotenv() # reads OPENAI_API_KEY from .env client = OpenAI() # key is picked up automatically response = client.responses.create( model="gpt-4o", # must be gpt-4o or later input="Summarize our Q2 pipeline in three sentences.", # user turn instructions="You are a concise sales analyst.", # system prompt ) print(response.output_text) # the model's reply as a plain string
The instructions parameter replaces the old {"role": "system", ...} dict. The input parameter holds the message content for single-turn calls.
output_text is a convenience property that aggregates all output_text items from the output list and returns their joined text (or an empty string if none exist).
Coffee can capture these single-turn API calls automatically, logging each request and response to your CRM without manual entry.
Maintain Conversation Memory Across Turns
The minimal example above works for one-off questions. Most real-world applications need to keep context across multiple exchanges, such as when a user asks a follow-up question that references a previous answer.
Multi-turn conversations pass prior context back to the model on each call. The Responses API accepts a list of role-keyed dicts under the input parameter, and this structure matches what Coffee's Agent ingests as activity notes.
history = [] def chat(user_message: str) -> str: history.append({"role": "user", "content": user_message}) response = client.responses.create( model="gpt-4o", input=history, # full conversation passed each turn instructions="You are a helpful sales assistant.", ) assistant_reply = response.output_text history.append({"role": "assistant", "content": assistant_reply}) return assistant_reply print(chat("Who is our top open opportunity?")) print(chat("What is their expected close date?"))
Common Pitfall: Conversation-State Loss. The
historylist lives in memory, so a process restart drops all context. Persisthistoryto a database or file between sessions and see the FAQ below for a concrete pattern.
Stream Responses Token by Token
Streaming surfaces tokens as the model generates them and reduces perceived latency in interactive tools. Pass stream=True and iterate the event stream in a loop.
The loop below includes early termination logic, which helps when a sentinel phrase or structure signals a complete answer before the model finishes.
with client.responses.create( model="gpt-4o", input="List the five biggest deals in our pipeline.", instructions="You are a concise sales analyst.", stream=True, # enables token-by-token delivery ) as stream: buffer = [] for event in stream: token = event.output_text_delta or "" print(token, end="", flush=True) buffer.append(token) # early termination: stop once the list is complete full_text = "".join(buffer) if full_text.count("\n") >= 5: stream.close() # releases the connection immediately break print() # newline after streamed output
Common Pitfall: Token-Cost Surprises. Streaming does not reduce token consumption, it only changes delivery timing. A response interrupted with
stream.close()still bills for every token generated so far. Set amax_output_tokensparameter to cap spend.
Return Structured JSON for CRM Fields
Structured JSON output removes fragile string parsing and produces objects that map directly to CRM fields such as contact name, company, and next step. This structure matches what Coffee's Agent uses when writing records back to Salesforce or HubSpot.
import json schema = { "type": "object", "properties": { "contact_name": {"type": "string"}, "company": {"type": "string"}, "next_step": {"type": "string"}, "close_date": {"type": "string", "format": "date"}, }, "required": ["contact_name", "company", "next_step", "close_date"], "additionalProperties": False, } response = client.responses.create( model="gpt-4o", input="Extract the CRM fields from: 'Follow up with Jane Doe at Acme Corp on 2026-09-01 to finalize the contract.'", instructions="Return only valid JSON matching the provided schema.", text=={ "format": { "type": "json_schema", # enforces structured output "name": "crm_fields", "schema": schema, "strict": True, # model must not deviate from schema } }, ) crm_data = json.loads(response.output_text) print(crm_data) # {"contact_name": "Jane Doe", "company": "Acme Corp", # "next_step": "Finalize the contract", "close_date": "2026-09-01"}
Production Checklist for Reliable Services
Moving from a script to a production service means handling transient failures, protecting credentials, and keeping an audit trail. Coffee's Agent can log rate-limit events and retry attempts as activity records, which gives operations teams full visibility without manual logging.
- Exponential back-off on rate limits. Catch
openai.RateLimitErrorand retry with delays of 1 s, 2 s, 4 s, and 8 s before raising. This pattern handles temporary capacity constraints on OpenAI's side. - Retry on transient errors. After rate limits, wrap calls in a loop that retries
openai.APIConnectionErrorandopenai.InternalServerErrorup to three times to cover network issues and short-lived server failures. - Rotate API keys on a schedule. Even with strong error handling, compromised credentials remain a major security risk. Generate a new key in the OpenAI dashboard before revoking the old one so you avoid downtime.
- Audit every call. Once your integration is resilient and secure, log the request ID from
response.idwith timestamps and token counts so you can attribute cost and debug issues.
Common Pitfall: API-Key Exposure. Never commit
.envfiles to version control. Add.envto.gitignoreimmediately and use a secrets manager such as AWS Secrets Manager or HashiCorp Vault in production environments.
Final Validation: Let Coffee Capture Every Interaction
The code above gives you a production-grade ChatGPT integration. The remaining gap is data capture, because every API call, extracted CRM field, and follow-up action lives only in your script's memory unless another system writes it to a system of record.
Coffee's Agent fills that gap by writing the structured payloads from your integration directly into your CRM. As shown in the examples above, Coffee turns model output into durable CRM updates so your team can trust that insights and next steps live alongside the right records.
Teams that have outgrown spreadsheets but do not want to maintain a legacy CRM can also run Coffee as a standalone AI-first CRM. In that setup, the Agent manages the entire system of record from day one.
Start your Coffee trial and remove manual CRM logging from your ChatGPT integration today.
Frequently Asked Questions
How do I keep my OpenAI API key secure in a Python project?
Store the key in an environment variable, not in source code. Locally, use a .env file loaded by python-dotenv and keep that file out of version control.
In production, inject the key through your platform's secrets manager, such as AWS Secrets Manager, Google Secret Manager, or HashiCorp Vault, so the value never touches disk on the server.
Rotate keys on a regular schedule. Generate the replacement key first, deploy it, then revoke the old one so you avoid downtime. If a key is ever committed to a public repository, revoke it immediately in the OpenAI dashboard and treat it as fully compromised.
How do I estimate the cost of a ChatGPT Python integration before going to production?
OpenAI bills per token, and a token is roughly four characters of English text. Use the tokenizer in the openai SDK to count tokens in your prompts before sending them, then multiply by the per-token rate for your chosen model on the OpenAI pricing page.
For streaming calls, set max_output_tokens to cap the output side. In production, log the usage object returned on every non-streaming response, which includes input_tokens, output_tokens, and total_tokens, and aggregate those figures daily to track spend.
Run a load test against a cheaper model first and extrapolate to your target model's rate. This approach gives a reliable upper-bound estimate before you commit to production traffic.
How do I persist conversation memory across Python sessions?
The simplest approach serializes the history list to JSON and writes it to a file or database row keyed by a session or user ID at the end of each turn. On the next session, read that record back and pass it as the input list to the Responses API.
For multi-user applications, store history in a database table with columns for session ID, role, content, and timestamp. Trim old turns when the list grows beyond your model's context window, and use a rolling window that keeps the most recent N turns.
When you need semantic retrieval instead of a raw transcript, embed each turn and store the vectors in a vector database. Retrieve the most relevant turns at query time instead of passing the full history.
Coffee handles conversation persistence automatically, capturing every turn as a structured CRM activity so you can skip the custom database code described above.


