Managing Responses
Every response is stored by default and can be retrieved or deleted using its id.
Opting Out of Storage
Set store=false to skip persisting a response. The response is returned normally but is not saved to history.
-
curl
-
Python (OpenAI SDK)
curl -X POST $BASE_URL/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-d '{
"model": "qwen3-32b-tool",
"input": "What is 2+2?",
"store": false
}'
response = client.responses.create(
model="qwen3-32b-tool",
input="What is 2+2?",
store=False,
)
# Response is returned but not persisted
|
Impact on conversation chaining
A non-stored response cannot be referenced by a later For example, if Turn 2 uses |
This is useful for one-off queries, e.g. classification or extraction, where you don’t need conversation continuity or later retrieval.
Retrieve a Response
Fetch a stored response by its ID.
-
curl
-
Python (OpenAI SDK)
curl $BASE_URL/v1/responses/resp_abc123 \
-H "Authorization: Bearer $AA_TOKEN"
response = client.responses.retrieve("resp_abc123")
print(response.id) # "resp_abc123"
print(response.status) # "completed"
print(response.output_text) # The assistant's response text
Response:
{
"id": "resp_abc123",
"object": "response",
"created_at": 1711000000,
"model": "qwen3-32b-tool",
"status": "completed",
"output": [...],
"usage": {...}
}
Delete a Response
Remove a stored response when it’s no longer needed.
-
curl
-
Python (OpenAI SDK)
curl -X DELETE $BASE_URL/v1/responses/resp_abc123 \
-H "Authorization: Bearer $AA_TOKEN"
client.responses.delete("resp_abc123") # returns None on success
Response:
{
"id": "resp_abc123",
"object": "response",
"deleted": true
}
Deletion Behavior
Deletion follows a retention policy built around two modes: soft delete (the default, reversible) and hard delete (admin-only, permanent). A plain DELETE is always a soft delete, and the 200 body above ("deleted": true) is returned in both modes.
Soft delete (default)
A standard DELETE marks the response as deleted by stamping a deleted_at timestamp. The row remains in the database but becomes invisible to normal access:
-
It can no longer be retrieved via
GET /v1/responses/{id}(returns404). -
It can no longer be used as a
previous_response_id; chaining from it fails with404 not_found_error. -
It is excluded from conversation listings.
Cascade to descendants: Soft-deleting a response also soft-deletes every subsequent response in its chain: any response whose ancestor_ids contain the deleted ID. Deleting a parent therefore removes the entire downstream subtree in one call, so you no longer need to delete chains leaf-to-root.
For example, in a chain resp_1 → resp_2 → resp_3, deleting resp_1 soft-deletes resp_1, resp_2, and resp_3 together. Deleting resp_2 removes resp_2 and resp_3 but leaves resp_1 intact.
Soft delete does not affect the parent conversation; the conversation row remains unchanged.
Hard delete (admin only)
Admins can permanently remove a response by passing hard_delete=true. This physically deletes the row(s) from the database; the data is unrecoverable.
-
curl
curl -X DELETE "$BASE_URL/v1/responses/resp_abc123?hard_delete=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
-
Hard delete also cascades to all descendants, including ones that were already soft-deleted.
-
A non-admin caller passing
hard_delete=truereceives403(permission_error/insufficient_permissions). -
If the target response is still being processed in the background, hard delete is blocked with
425 Too Early(too_early_error/response_in_progress). Wait until processing completes or fails, then retry.
|
Hard delete is irreversible
Hard delete exists for compliance scenarios (e.g. GDPR erasure requests) where data must be physically purged. Once a response is hard-deleted it cannot be recovered. For routine deletions, prefer the default soft delete. |
Admin: retrieve a soft-deleted response
Admins can read soft-deleted responses by passing include_deleted=true. This is intended for audit and recovery workflows and is logged.
-
curl
curl "$BASE_URL/v1/responses/resp_abc123?include_deleted=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
Non-admin callers passing include_deleted=true receive 403.
Admin: recover a soft-deleted response
Because soft delete is reversible, admins can restore a soft-deleted response with PATCH /v1/responses/{id} and recovery_from_delete=true. Recovery cascades to all soft-deleted descendants, restoring the subtree that was removed together.
-
curl
curl -X PATCH "$BASE_URL/v1/responses/resp_abc123?recovery_from_delete=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
-
Returns the recovered response object.
-
Non-admin callers receive
403; omittingrecovery_from_delete=truereturns400. -
Hard-deleted responses cannot be recovered; there is no row left to restore.
Response Metadata
You can attach key-value metadata to any response for tagging, filtering, or tracking:
-
curl
-
Python (OpenAI SDK)
curl -X POST $BASE_URL/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-d '{
"model": "qwen3-32b-tool",
"input": "Summarize the quarterly report.",
"metadata": {
"team": "finance",
"request_source": "slack-bot"
}
}'
response = client.responses.create(
model="qwen3-32b-tool",
input="Summarize the quarterly report.",
metadata={
"team": "finance",
"request_source": "slack-bot",
},
)
print(response.metadata) # {"team": "finance", "request_source": "slack-bot"}
Constraints:
| Limit | Value |
|---|---|
Maximum keys |
16 |
Maximum key length |
64 characters |
Maximum value length |
512 characters |
Value type |
Strings only |
Metadata is echoed back in the response object and persisted with the response.
Conversations API
Conversations let you group related responses together and manage them as a unit. There are two ways to use conversations:
-
Explicitly, create a conversation up front, then reference it in requests via the
conversationfield. -
Implicitly, when a response belongs to a conversation and you chain from it with
previous_response_id, the conversation ID is inherited automatically.
Assigning Responses to a Conversation
Pass the conversation field when creating a response to assign it to an existing conversation. You can pass either a bare string or an object:
-
curl
-
Python (OpenAI SDK)
# Assign a response to an existing conversation.
# Replace CONVERSATION_ID with the id of your conversation.
curl -X POST $BASE_URL/v1/responses \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-d '{
"model": "qwen3-32b-tool",
"input": "Hello!",
"conversation": "CONVERSATION_ID"
}'
import httpx
# Create a conversation
conv = httpx.post(
f"{BASE_URL}/v1/conversations",
headers={"Authorization": f"Bearer {AA_TOKEN}"},
).json()
# Assign a response to it
response = client.responses.create(
model="qwen3-32b-tool",
input="Hello!",
extra_body={"conversation": conv["id"]},
)
When you chain from a response that belongs to a conversation using previous_response_id, the follow-up response automatically inherits the conversation; you don’t need to pass conversation again.
Create a Conversation
-
curl
-
Python
curl -X POST $BASE_URL/v1/conversations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN"
import httpx
conv = httpx.post(
f"{BASE_URL}/v1/conversations",
headers={"Authorization": f"Bearer {AA_TOKEN}"},
).json()
print(conv["id"]) # "conv_abc123"
Response:
{
"id": "conv_abc123",
"object": "conversation",
"metadata": {},
"created_at": 1711000000,
"updated_at": 1711000000
}
You can optionally pass metadata (key-value string pairs, max 16 keys) on create or update it later via POST /v1/conversations/{id}. Metadata follows the same constraints as response metadata.
Retrieve a Conversation
-
curl
-
Python
curl $BASE_URL/v1/conversations/{conversation_id} \
-H "Authorization: Bearer $AA_TOKEN"
import httpx
conv = httpx.get(
f"{BASE_URL}/v1/conversations/{conversation_id}",
headers={"Authorization": f"Bearer {AA_TOKEN}"},
).json()
List Conversations
-
curl
-
Python
curl "$BASE_URL/v1/conversations?limit=10&offset=0&order=desc" \
-H "Authorization: Bearer $AA_TOKEN"
import httpx
result = httpx.get(
f"{BASE_URL}/v1/conversations",
params={"limit": 10, "offset": 0, "order": "desc"},
headers={"Authorization": f"Bearer {AA_TOKEN}"},
).json()
for conv in result["data"]:
print(conv["id"], conv["metadata"])
Response:
{
"object": "list",
"data": [
{
"id": "conv_abc123",
"object": "conversation",
"metadata": {},
"created_at": 1711000000,
"updated_at": 1711000000
}
],
"has_more": false,
"first_id": "conv_abc123",
"last_id": "conv_abc123"
}
| Parameter | Type | Default | Description |
|---|---|---|---|
|
integer |
|
Maximum conversations to return (max 100) |
|
integer |
|
Number of conversations to skip |
|
string |
|
Sort by |
|
string |
|
Return only conversations with matching |
|
boolean |
|
Admin only. Include soft-deleted conversations in the list. Non-admin callers passing |
To list conversations for one app, create conversations with an application metadata value and pass the same value as metadata.application when listing.
-
curl
-
Python
curl -X POST $BASE_URL/v1/conversations \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AA_TOKEN" \
-d '{"metadata": {"application": "legal-agent"}}'
curl "$BASE_URL/v1/conversations?metadata.application=legal-agent" \
-H "Authorization: Bearer $AA_TOKEN"
import httpx
httpx.post(
f"{BASE_URL}/v1/conversations",
json={"metadata": {"application": "legal-agent"}},
headers={"Authorization": f"Bearer {AA_TOKEN}"},
)
result = httpx.get(
f"{BASE_URL}/v1/conversations",
params={"metadata.application": "legal-agent"},
headers={"Authorization": f"Bearer {AA_TOKEN}"},
).json()
List Responses in a Conversation
Fetch all responses belonging to a conversation. Each item includes the full response data plus tree metadata (ancestor_ids, depth) and the original request_input.
-
curl
-
Python
# Ascending order (default)
curl "$BASE_URL/v1/conversations/{conversation_id}/responses" \
-H "Authorization: Bearer $AA_TOKEN"
# Descending order
curl "$BASE_URL/v1/conversations/{conversation_id}/responses?order=desc" \
-H "Authorization: Bearer $AA_TOKEN"
# Admin only: include soft-deleted responses
curl "$BASE_URL/v1/conversations/{conversation_id}/responses?include_deleted=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
import httpx
response = httpx.get(
f"{BASE_URL}/v1/conversations/{conversation_id}/responses",
headers={"Authorization": f"Bearer {AA_TOKEN}"},
)
data = response.json()
for item in data["data"]:
print(f"{item['id']} (depth={item['depth']}): {item['status']}")
Response:
{
"object": "list",
"data": [
{
"id": "resp_001",
"object": "response",
"status": "completed",
"model": "qwen3-32b-tool",
"output": [...],
"ancestor_ids": [],
"depth": 0,
"request_input": [{"type": "message", "role": "user", "content": "Hello"}]
},
{
"id": "resp_002",
"object": "response",
"status": "completed",
"model": "qwen3-32b-tool",
"output": [...],
"ancestor_ids": ["resp_001"],
"depth": 1,
"request_input": [{"type": "message", "role": "user", "content": "Follow up"}]
}
]
}
| Parameter | Type | Default | Description |
|---|---|---|---|
|
string |
|
Sort by |
|
boolean |
|
Admin only. Include soft-deleted responses in the list. Non-admin callers passing |
Delete a Conversation
-
curl
-
Python
curl -X DELETE $BASE_URL/v1/conversations/{conversation_id} \
-H "Authorization: Bearer $AA_TOKEN"
import httpx
httpx.delete(
f"{BASE_URL}/v1/conversations/{conversation_id}",
headers={"Authorization": f"Bearer {AA_TOKEN}"},
)
Response:
{
"id": "conv_abc123",
"object": "conversation",
"deleted": true
}
Conversation deletion follows the same retention policy as responses, with the same options: soft delete is the default, hard_delete=true and recovery are admin-only, and admins can read soft-deleted data with include_deleted=true (see List Responses in a Conversation).
The key difference: deleting a single response only cascades to that response’s descendants (later responses in its chain), leaving its ancestors intact. Deleting a conversation instead removes every response in it, the entire response tree, including ancestors, not just one chain.
-
The conversation and all its responses are marked as deleted (cascade) but remain in the database.
-
Soft-deleted responses cannot be retrieved via
GET /v1/responses/{id}or used asprevious_response_id. -
You do not need to delete individual responses first; the cascade handles it.
-
Deleting an already-deleted or non-existent conversation returns
404.
Hard delete (admin only)
Admins can permanently remove a conversation and every history entry it owns by passing hard_delete=true:
-
curl
curl -X DELETE "$BASE_URL/v1/conversations/{conversation_id}?hard_delete=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
-
The conversation row and all associated responses are physically deleted and cannot be recovered.
-
A non-admin caller passing
hard_delete=truereceives403. -
If any response in the conversation is still being processed in the background, hard delete is blocked with
425 Too Early(conversation_has_in_progress_responses).
Recovery (admin only)
A soft-deleted conversation can be restored with PATCH /v1/conversations/{id} and recovery_from_delete=true. Recovery restores the conversation and cascades to its soft-deleted responses.
-
curl
curl -X PATCH "$BASE_URL/v1/conversations/{conversation_id}?recovery_from_delete=true" \
-H "Authorization: Bearer $AA_ADMIN_TOKEN"
Non-admin callers receive 403; hard-deleted conversations cannot be recovered.