Querying Outbound Dialing Results and Attempt Records in Genesys Cloud CX

Querying Outbound Dialing Results and Attempt Records in Genesys Cloud CX

What This Guide Covers

You will learn how to programmatically extract, filter, and correlate outbound campaign attempt records, contact dispositions, and dialer performance metrics using the Genesys Cloud Outbound API. The end result is a reliable, paginated data pipeline that accurately maps attempt outcomes to agent performance and campaign ROI without data skew, pagination drift, or compliance filtering errors.

Prerequisites, Roles & Licensing

  • Licensing Tier: CX 1, CX 2, or CX 3 with the Outbound Campaigns add-on enabled. Preview Dialing and Power Dialing share the same attempt data model.
  • IAM Permissions:
    • Campaign:View
    • Outbound:View
    • Contact:View
    • Analytics:View (required if correlating to WEM or Speech Analytics)
  • OAuth Scopes:
    • outbound:campaign:read
    • outbound:attempt:read
    • outbound:contact:read
    • analytics:report:read (for cross-referencing performance dashboards)
  • External Dependencies: REST client or ETL orchestration framework, ISO 8601 timezone handling library, and a data sink capable of handling JSON array pagination. Familiarity with the outbound contact lifecycle and DNC compliance logic is required.

The Implementation Deep-Dive

1. Constructing the Base Attempt Query with Correct Temporal Boundaries

The outbound attempt data model in Genesys Cloud CX partitions records by creation timestamp, not completion timestamp. The dialer generates an attempt record the moment it selects a contact from the queue, regardless of whether the call connects, fails, or is abandoned. This architectural choice enables horizontal sharding across high-throughput dialer nodes, but it fundamentally changes how you must structure your temporal filters.

You will use the GET /api/v2/outbound/attempts endpoint. The request must include campaignId, dateFrom, and dateTo. Genesys expects ISO 8601 formatted strings in UTC. The dateFrom parameter is inclusive, while dateTo is exclusive. This boundary behavior is non-negotiable and dictates your pagination strategy.

Production Request Example:

GET /api/v2/outbound/attempts?campaignId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&dateFrom=2024-11-01T00:00:00Z&dateTo=2024-11-02T00:00:00Z&pageNumber=1&pageSize=1000
Authorization: Bearer <ACCESS_TOKEN>
Accept: application/json

Response Payload Structure:

{
  "items": [
    {
      "attemptId": "att-9876543210",
      "contactId": "cont-1234567890",
      "campaignId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "attemptDate": "2024-11-01T08:14:22.301Z",
      "completedDate": "2024-11-01T08:15:45.102Z",
      "status": "COMPLETED",
      "disposition": "INTERESTED",
      "conversationId": "conv-5566778899",
      "agentId": "agent-00112233",
      "failureReason": null,
      "retryCount": 0
    }
  ],
  "pageNumber": 1,
  "pageSize": 1000,
  "totalCount": 14520
}

The Trap: Querying live, actively dialing campaigns using dateTo set to now(). Because attempt records are created at queue selection time, a contact picked up at 23:59:50 UTC may not complete until 00:05:10 UTC the following day. If you run a daily export with an exclusive dateTo boundary, those late-completing calls will either be duplicated in the next day’s pull or truncated depending on how your ETL handles the attemptDate versus completedDate mismatch. Under high dialer throughput, this creates a phantom data gap that breaks campaign ROI calculations.

Architectural Reasoning: Genesys partitions the attempt table by attemptDate to optimize write performance during peak dialing windows. Reads are routed to the correct shard based on that timestamp. If you require completion-time filtering, you must pull the full creation window and apply a secondary filter on completedDate in your data layer. Never rely on the API to filter by completion time. The API does not support completedDate as a query parameter for a reason: it would require cross-shard joins that degrade latency exponentially.

2. Filtering by Disposition and Call Outcome

The status field indicates the dialer outcome, while the disposition field captures the agent or predictive dialer result. These are decoupled by design. A call can have status: COMPLETED but disposition: null if the agent hangs up before selecting a disposition. Conversely, a predictive dialer can auto-dispose a contact as MACHINENUMBER without ever routing to an agent.

You will filter using the status and disposition query parameters. Valid status values include COMPLETED, FAILED, ABANDONED, NOANSWER, DNC, and INVALID. The disposition parameter accepts exact string matches. Wildcards are not supported.

Production Request Example with Filters:

GET /api/v2/outbound/attempts?campaignId=a1b2c3d4-e5f6-7890-abcd-ef1234567890&dateFrom=2024-11-01T00:00:00Z&dateTo=2024-11-02T00:00:00Z&status=COMPLETED&disposition=INTERESTED&pageNumber=1&pageSize=1000
Authorization: Bearer <ACCESS_TOKEN>
Accept: application/json

The Trap: Hardcoding disposition strings in your extraction pipeline. Campaign administrators frequently rename, merge, or archive dispositions to align with shifting business objectives. A pipeline that queries disposition=SALE will return zero records the moment an admin renames it to CONVERTED_LEAD. This breaks reporting continuity and creates false negatives in downstream analytics.

Architectural Reasoning: Dispositions are campaign-scoped metadata, not platform-wide constants. You must dynamically resolve the active disposition taxonomy before querying attempts. Use the GET /api/v2/outbound/campaigns/{campaignId}/dispositions endpoint to fetch the current mapping. Cache the response with a TTL of 15 minutes, as disposition changes are infrequent but do occur during campaign tuning. Build your extraction logic to accept a disposition ID or name, and map it against the live campaign configuration at runtime. This eliminates hardcoded string dependencies and ensures your pipeline adapts to administrative changes without redeployment.

3. Implementing Safe Pagination and Cursor Management

The outbound attempts endpoint uses offset-based pagination (pageNumber and pageSize). The maximum pageSize is 1000. The totalCount field in the response header indicates the total number of records matching your query at the moment of the first request. You will iterate pageNumber incrementally until pageNumber * pageSize >= totalCount.

Pagination Loop Logic:

page = 1
page_size = 1000
total_count = None

while True:
    response = requests.get(
        f"/api/v2/outbound/attempts?campaignId={campaign_id}&dateFrom={date_from}&dateTo={date_to}&pageNumber={page}&pageSize={page_size}",
        headers=headers
    )
    data = response.json()
    items = data["items"]
    total_count = data["totalCount"]
    
    process_batch(items)
    
    if page * page_size >= total_count:
        break
    page += 1

The Trap: Assuming totalCount remains static throughout the pagination cycle. When querying historical data, totalCount is stable. When querying a campaign that is still dialing, new attempt records are inserted into the shard continuously. The API captures the snapshot at page 1, but subsequent pages may return overlapping or shifted records because the underlying dataset changed between requests. This causes duplicate processing or missed records in your data warehouse.

Architectural Reasoning: Offset-based pagination is inherently unstable on write-heavy tables. Genesys documents this explicitly for outbound attempts. You must enforce a strict temporal boundary that excludes the current dialing window. Schedule your extraction jobs to run against dateTo set to the end of the previous business day in UTC. This guarantees a closed dataset where totalCount is immutable across all pages. If you require near-real-time monitoring, use the Outbound Campaigns WebSockets or the /api/v2/outbound/campaigns/{id} metrics endpoint, which provides aggregated counters without row-level pagination drift.

4. Correlating Attempts to Agent Performance and WEM Data

Attempt records contain agentId and conversationId fields, but only when the call successfully routes to a queue member. Failed dial attempts, DNC suppressions, and invalid number hits do not generate conversations. You will use conversationId to join attempt data with Workforce Engagement Management (WEM) recordings, Speech Analytics transcripts, and interaction quality scores.

Cross-Reference Query Pattern:

GET /api/v2/recordings/{conversationId}
GET /api/v2/analytics/conversations/queries

The Trap: Assuming every COMPLETED attempt has a valid conversationId. In preview dialing, agents may click “Call” but immediately hang up before the platform registers a full conversation lifecycle. In power dialing, predictive routing can generate a COMPLETED status with a disposition but no associated conversationId if the call was answered by an answering machine and auto-disposed by the dialer logic. Joining on attemptId instead of conversationId creates orphaned records in your quality management pipeline.

Architectural Reasoning: The outbound data model separates dialer outcomes from interaction outcomes. attemptId tracks the dialer lifecycle. conversationId tracks the telephony and media lifecycle. You must filter for status == COMPLETED AND conversationId IS NOT NULL before initiating WEM or Speech Analytics joins. This two-step validation ensures you only route records that actually generated media streams. When building your correlation pipeline, implement a fallback handler that logs attempts with missing conversation IDs for compliance auditing, but excludes them from quality scoring calculations. This prevents null pointer exceptions in your analytics engine and maintains accurate agent performance baselines.

Validation, Edge Cases & Troubleshooting

Edge Case 1: DNC and Compliance Filtering Skew

The Failure Condition: Campaign dashboard reports show 12,000 attempts for the day, but your API extraction pipeline returns 9,800 records. The discrepancy grows proportionally with list size.
The Root Cause: Genesys Cloud CX enforces DNC compliance at the dialer routing layer. When a contact matches a global DNC list, a campaign-specific suppression list, or a regulatory exclusion (TCPA, GDPR), the dialer marks the attempt as FAILED or DNC and immediately suppresses further retries. The API returns these records, but many ETL frameworks automatically drop rows where disposition is null or failureReason contains compliance codes. Your pipeline is silently filtering compliant suppressions as invalid data.
The Solution: Explicitly handle failureReason and status combinations in your ingestion logic. Map status: DNC, status: INVALID, and failureReason values like DNC_GLOBAL, DNC_CAMPAIGN, or REGULATORY_BLOCK to your compliance taxonomy. Do not drop these records. They represent legitimate dialer activity and are required for accurate contact penetration rates. If your reporting tool requires a disposition value, normalize compliance suppressions to a standard taxonomy code (e.g., SUPP_DNC) before loading into the data warehouse.

Edge Case 2: Timezone and DST Shifts in Attempt Dates

The Failure Condition: Duplicate attempt records appear in consecutive daily exports, or entire days of data disappear during March and November.
The Root Cause: The outbound API returns all timestamps in UTC. Campaigns are configured with a local timezone. When you construct dateFrom and dateTo boundaries using naive local time conversions, daylight saving time transitions shift the UTC boundary by an hour. An inclusive dateFrom of 2024-03-10T00:00:00Z in a campaign configured for US Eastern Time actually starts at 19:00:00 EST the previous day. This causes overlap with the prior day’s export or truncation of early morning attempts.
The Solution: Query exclusively in UTC. Convert all campaign timezone configurations to UTC offsets at runtime. Use inclusive dateFrom and exclusive dateTo boundaries strictly. When normalizing data for reporting, apply timezone conversion in your data transformation layer, not in your API request. Implement a validation check that compares the attemptDate against the completedDate. If completedDate precedes attemptDate by more than 10 minutes, flag the record for manual review, as this indicates a clock skew or system clock synchronization failure on the dialer node.

Edge Case 3: Rate Limiting and Throttling on High-Volume Campaigns

The Failure Condition: Extraction jobs fail with HTTP 429 Too Many Requests errors during peak export windows. The pipeline enters a retry loop, consumes available credits, and eventually times out.
The Root Cause: Genesys Cloud enforces per-tenant and per-endpoint rate limits on the outbound API. Bursting pagination requests without respecting the Retry-After header triggers exponential backoff. High-volume campaigns (50,000+ attempts per day) generate large totalCount values, forcing your pipeline to issue dozens of sequential requests within a short window. The API gateway interprets this as a denial-of-service pattern and throttles the client IP or API key.
The Solution: Implement a token bucket rate limiter that caps outbound requests at 5 per second per campaign. Parse the Retry-After header on every 429 response and enforce the exact delay. Stagger pagination requests by campaign ID if extracting multiple campaigns concurrently. Use connection pooling with keep-alive headers to reduce TCP handshake overhead. Schedule bulk exports during off-peak hours (02:00-04:00 UTC) when dialer throughput is lowest and API capacity is highest. Monitor the X-RateLimit-Remaining header to dynamically adjust your pagination speed based on available capacity.

Official References