Skip to main content
This guide walks through submitting your first analysis, polling it to completion, and reading the result. We’ll start with mock fixtures (free, instant) and end with real audio.

What you need

  • A sandbox API key (pk_sandbox_…)
  • An HTTP client (curl, Postman, or your language’s HTTP library)

Step 1 — Mock fixture (free, ~1 second)

Submit a deterministic test analysis. No real audio required.
curl -X POST https://api.goparlay.io/v1/analyses \
  -H "Authorization: Bearer pk_sandbox_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "org_id": "demo-acme",
    "rep_id": "alex-smith",
    "recording_url": "mock://perfect-pitch"
  }'
You get back a 202 with the queued job:
{
  "id": "f2782fc7-5355-48bf-a314-d6bf90bc4907",
  "status": "queued",
  "created_at": "2026-04-24T18:10:36.284444+00:00"
}
Org and rep are created automatically if they don’t exist yet. You don’t need to call POST /v1/orgs first for analyses to work.

Step 2 — Poll until complete

curl https://api.goparlay.io/v1/analyses/<id> \
  -H "Authorization: Bearer pk_sandbox_YOUR_KEY"
status will progress: queuedprocessingcompleted. Mock fixtures usually complete in under a second. When status === "completed", the response includes the full analysis. The same shape comes back for mock://* fixtures and real audio — write your decoder once.
{
  "id": "ec1851ba-4037-41bf-9ad5-d64391c11ab4",
  "status": "completed",
  "duration_seconds": 312,
  "transcript": { /* utterances, word_count, timing */ },
  "analysis": {
    "prospect_name": "Casey Morgan",
    "recording_title": "Closed Casey",

    "double_down": "The pacing on the value prop was perfect — keep doing exactly that.",
    "ai_summary": "Strong opening, clean discovery, textbook objection handling, confident close.",
    "ai_summary_v5": "Strong opening, clean discovery, textbook objection handling, confident close.",

    "questions_asked": 11,
    "filler_word_count": 3,
    "words_per_minute": 142,

    "overall_score": 95,
    "clarity_score": 94,
    "influence_score": 96,
    "objection_score": 92,
    "discovery_score": 95,
    "delivery_score": 97,
    "close_score": 96,

    "feedback_v5": {
      "clarity":   { "positive": "Articulated the ROI in one clean sentence", "negative": "A few jargon moments" },
      "influence": { "positive": "Anchored to a peer customer", "negative": "Could have asked them to articulate impact" },
      "objection": { "positive": "Validated price concern, reframed against cost of inaction", "negative": "Rushed the timing objection" },
      "discovery": { "positive": "8 minutes of open-ended questions surfaced 3 pain points", "negative": "Skipped quantifying the pain" },
      "delivery":  { "positive": "Steady pace, clear enunciation", "negative": "A few long monologues" },
      "close":     { "positive": "Asked for next step explicitly", "negative": "Could have summarized agreed value first" }
    },

    "action_plan_v5": {
      "double_down_implementation": "Open every discovery call with the same one-sentence ROI anchor.",
      "general_communication_improvement": "Cap monologues at 45s; ask a check-in question after.",
      "quoted_principle": "\"People support what they help create.\" Make the prospect co-author the value."
    },

    "organization_prompt_summary": null,
    "organization_feedback": null,
    "master_prompt_id": null,
    "master_prompt_version": "v5",
    "persona_snapshot": null,
    "synthesis_snapshot": null
  }
}

Decoding tips

  • Six pillars: clarity, influence, objection, discovery, delivery, close. Each appears as a flat *_score field (0–100) AND inside feedback_v5.<pillar> with positive + negative strings.
  • overall_score is the rounded average of the six pillars — computed server-side, not from the model.
  • KPIs (questions_asked, filler_word_count, words_per_minute) are flat at the top of analysis. Not nested under kpis.
  • feedback_v5 + action_plan_v5 keep the _v5 suffix so future revisions can ship _v6 without breaking decoders.
  • ai_summary and ai_summary_v5 carry the same string today. Use either.

Step 3 — Real audio

Replace the mock URL with a publicly reachable HTTPS URL to an audio file:
{
  "org_id": "demo-acme",
  "rep_id": "alex-smith",
  "recording_url": "https://my-cdn.com/calls/2026-04-24-acme.mp3",
  "options": {
    "environment": "video_conference",
    "sales_motion": "outbound_ae",
    "recording_type": "discovery_call"
  }
}
Requirements for the URL:
  • HTTPS (no plain HTTP)
  • No additional auth headers required (pre-signed S3 / GCS URLs are perfect)
  • Codec: MP3, WAV, M4A, or FLAC
  • Length: 10 seconds to 60 minutes
Latency: typically 30–90 seconds end-to-end for a 5-minute call.

Step 4 — Stop polling, use webhooks

Polling works for prototyping but isn’t sustainable for production. Register a webhook once and Parlay will notify you when each analysis completes:
curl -X POST https://api.goparlay.io/v1/webhooks \
  -H "Authorization: Bearer pk_sandbox_YOUR_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/parlay-webhook",
    "enabled_events": ["analysis.completed", "analysis.failed"]
  }'
The response includes a signing_secret (shown once — store it). Verify every webhook with HMAC-SHA256. See the webhooks guide.

Common pitfalls

Parlay fetches the file from the URL you provide. If the URL requires auth (Authorization header, custom token), it will fail. Use a pre-signed URL instead.
org_id + rep_id + recording_url does NOT deduplicate. Use Idempotency-Key with the same value on retries to avoid duplicates.
Treat these like foreign keys, not display names. Pass stable opaque identifiers from your system:
GoodWhy
Salesforce User ID (0051a000001wQa3AAE)Immutable, globally unique
HubSpot Owner ID (12345678)Same
Your internal UUID (usr_a1b2c3d4)Same
Email (alex@acme.com)Stable enough — but breaks on email change
AvoidWhy
First names (alex, brooke)Two reps with the same name collide; rename has no migration
Display slugs (alex-smith)Better than first names but still display-shaped — treat as last resort
Once you set rep_id for a person, treat it as immutable. If it changes, all historical analyses still reference the old value — there’s no built-in “rename” operation today.Querying without org_id: GET /v1/analyses?rep_id=... resolves the rep partner-wide. If the same rep_id exists under multiple orgs, you’ll get a 400 ambiguous_rep_id — pass org_id to disambiguate. Solo-rep partners (one default org) never hit this.
Likely the audio is corrupt or unreachable. Call GET /v1/analyses/:id and check the error field. The crash recovery scanner marks stuck jobs failed automatically after 10 min.
Parlay caps at 60 minutes. For longer calls, split before upload. There’s no built-in concatenation across analyses (yet).

What’s next

Rep intelligence

Persona, methodology, and synthesis at scale

Webhooks

Stop polling — get notified