Setting wrap-up codes via GraphQL gateway after conversation termination

Does anyone know the correct sequence for applying wrap-up codes programmatically once an interaction has reached the terminated state?

I am maintaining a GraphQL gateway that wraps Genesys Cloud REST APIs. My resolvers use Apollo Server and DataLoader to batch requests. The issue arises when I attempt to set a wrap-up code for a conversation that has already been closed by the agent.

My current resolver logic fetches the conversation details, confirms the state is terminated, and then attempts a PUT request to update the wrap-up code. However, the API consistently rejects this with a 409 Conflict.

Here is the relevant snippet from my Node.js service layer:

const response = await axios.put(
 `https://${orgDomain}.mygenesys.cloud/api/v2/interactions/conversations/${conversationId}/wrapup`,
 {
 wrapUpCode: { id: 'valid-wrapup-code-id' },
 reason: 'Resolved' // Attempting to pass reason for audit
 },
 { headers: { Authorization: `Bearer ${token}` } }
);

The error payload returned is:

“The conversation is not in a state that allows this operation. Current state: terminated. Required state: active or wrapping.”

I understand that once a conversation is fully terminated, the window to set the wrap-up code closes. However, I need to handle cases where the agent clicks ‘End Call’ but the wrap-up was skipped or failed client-side. My gateway needs to retroactively apply the code for reporting consistency.

Is there a specific endpoint or a different HTTP method I should be using? Or do I need to intercept the conversation.state change via EventBridge and apply the wrap-up code before the final terminated state is committed? I prefer not to rely on webhooks for this critical path if a synchronous API call exists.

You need to abandon the GraphQL gateway pattern for this specific mutation. The Genesys Cloud API enforces strict state transitions where wrap-up codes can only be applied during the reserved or completed states, not after terminated. Your Apollo resolvers are likely hitting a 409 Conflict or 400 Bad Request because the underlying REST call PATCH /api/v2/conversations/{conversationId}/participants/{participantId} rejects the payload once the conversation lifecycle ends.

Instead of patching an already dead conversation, you should inject the wrap-up code earlier in the flow. If you are building a Slack bot or webhook bridge, listen for the conversation:participant:wrapevent webhook or the conversation:updated event with type: "wrapup". Capture the wrapupCode object at that moment. If you must retroactively assign codes for reporting, do not use the conversation participant API. Use the Analytics API to update custom attributes on the interaction record, or better yet, use the POST /api/v2/analytics/conversations/details/queue endpoint with a custom filter to tag the interaction post-hoc.

Here is how you should structure the participant update request before termination to avoid the 400 error:

PATCH /api/v2/conversations/{conversationId}/participants/{participantId}
{
 "wrapup": {
 "code": {
 "id": "your-wrapup-code-id",
 "value": "Customer Issue Resolved"
 },
 "duration": 30000,
 "notes": "Resolved via Slack integration"
 }
}

If the conversation is already terminated, this endpoint will fail. You need to shift your logic to intercept the wrap-up event while the participant state is still wrapup or completed. Relying on GraphQL to fix state after the fact is an architectural anti-pattern here. The REST API does not support retroactive wrap-up code assignment on terminated conversations.

This looks like a hard state constraint you cannot bypass with GraphQL batching. You must apply the wrap-up code before the conversation transitions to terminated using the participant update endpoint.

Warning: Attempting this after termination will always result in a 409 Conflict.

Make sure you stop trying to force state transitions that the platform explicitly blocks. The previous suggestions are correct about the 409 Conflict, but they miss the operational reality of data engineering. You cannot patch a terminated conversation for wrap-up codes. The API contract is strict. If your GraphQL gateway is receiving data after termination, your timing logic is broken. I deal with this in my Python ETL pipelines by pulling from the Analytics API instead of trying to mutate live state. Use GET /api/v2/analytics/conversations/details/query with a timeRange covering your batch window. Filter by conversation.id and state: terminated. This returns the final state snapshot. Parse the JSON response in pandas. Look for the wrappers array in the participant details. If it is empty, flag it as missing data in your downstream S3 export. Do not rely on real-time mutations for historical reporting. Your Apollo DataLoader is caching stale state or hitting race conditions. Here is how I extract the final wrap-up status reliably:

import requests
import json

headers = {"Authorization": f"Bearer {token}"}
payload = {
 "timeRange": "now-1h",
 "filter": [{"type": "field", "field": "state", "operator": "eq", "value": "terminated"}],
 "select": ["conversation.id", "participants", "participants.wrappers"]
}

response = requests.post(
 "https://api.mypurecloud.com/api/v2/analytics/conversations/details/query",
 headers=headers,
 json=payload
)
data = response.json()
for entity in data["entities"]:
 for p in entity["participants"]:
 wrappers = p.get("wrappers", [])
 if not wrappers:
 print(f"Missing wrap-up for {entity['id']}")

This approach avoids the 409 entirely. It treats the conversation as immutable history. Your gateway should query this endpoint for reporting, not try to mutate dead objects. The routing config does not care about your GraphQL schema. Respect the state machine.

The suggestion above correctly identifies the state constraint, but the real issue is likely how your GraphQL gateway handles the asynchronous nature of the terminated event versus the synchronous patch request. In my Twilio to Architect migration work, I’ve seen similar timing issues where the webhook triggers after the state has already hardened. You need to intercept the wrap-up logic before the final state transition. If you are using the PureCloudPlatformClientV2 SDK in Node.js for your resolver, ensure you are using the putConversationParticipantWrapup method with the correct scope conversation:write. However, if you are strictly bound to the terminated state, you cannot patch the conversation. Instead, you must use the POST /api/v2/conversations/{conversationId}/participants/{participantId}/wrapup endpoint during the completed state. Here is the correct payload structure you should be sending via your DataLoader batch:

const wrapupPayload = {
 wrapupCode: {
 id: "your-wrapped-up-code-id-from-api" // Ensure this is a valid UUID from your org
 },
 reason: "Automated wrap-up via GraphQL"
};

// Inside your Apollo Resolver
try {
 const response = await platformClient.ConversationsApi.putConversationParticipantWrapup(conversationId, participantId, wrapupPayload);
 if (response.status !== 204) {
 throw new Error(`Failed to set wrap-up: ${response.status} - ${response.body}`);
 }
} catch (error) {
 // Log the error and handle the 409 Conflict explicitly
 console.error("Wrap-up failed due to state conflict:", error);
}

If the conversation is already terminated, this will fail. You must adjust your Architect flow to trigger the external API call during the reserved or completed state using a Data Action, not after the fact. The GraphQL gateway cannot bypass the REST API’s immutable state rules. Check your event listener timing in the gateway.