> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goparlay.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Submit your first analysis

> Mock fixture → poll → real audio. Ten minutes start to finish.

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.

<CodeGroup>
  ```bash cURL theme={null}
  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"
    }'
  ```

  ```javascript Node theme={null}
  const job = await fetch("https://api.goparlay.io/v1/analyses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.PARLAY_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      org_id: "demo-acme",
      rep_id: "alex-smith",
      recording_url: "mock://perfect-pitch",
    }),
  }).then((r) => r.json());

  console.log(job.id);
  ```

  ```python Python theme={null}
  import os, uuid, requests

  job = requests.post(
      "https://api.goparlay.io/v1/analyses",
      headers={
          "Authorization": f"Bearer {os.environ['PARLAY_API_KEY']}",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "org_id": "demo-acme",
          "rep_id": "alex-smith",
          "recording_url": "mock://perfect-pitch",
      },
  ).json()

  print(job["id"])
  ```
</CodeGroup>

You get back a 202 with the queued job:

```json theme={null}
{
  "id": "f2782fc7-5355-48bf-a314-d6bf90bc4907",
  "status": "queued",
  "created_at": "2026-04-24T18:10:36.284444+00:00"
}
```

<Note>
  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.
</Note>

## Step 2 — Poll until complete

```bash theme={null}
curl https://api.goparlay.io/v1/analyses/<id> \
  -H "Authorization: Bearer pk_sandbox_YOUR_KEY"
```

`status` will progress: `queued` → `processing` → `completed`. 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.

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```bash theme={null}
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](/guides/webhooks).

## Common pitfalls

<AccordionGroup>
  <Accordion title="`recording_url` returns 404 to Parlay">
    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.
  </Accordion>

  <Accordion title="Same call submitted twice creates two analyses">
    `org_id + rep_id + recording_url` does NOT deduplicate. Use `Idempotency-Key` with the same value on retries to avoid duplicates.
  </Accordion>

  <Accordion title="Choosing org_id and rep_id values">
    **Treat these like foreign keys, not display names.** Pass stable opaque identifiers from your system:

    | Good                                      | Why                                        |
    | ----------------------------------------- | ------------------------------------------ |
    | 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 |

    | Avoid                          | Why                                                                     |
    | ------------------------------ | ----------------------------------------------------------------------- |
    | 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.
  </Accordion>

  <Accordion title="Status stuck on `processing` for >10 min">
    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.
  </Accordion>

  <Accordion title="`recording_too_long` — splitting an audio file">
    Parlay caps at 60 minutes. For longer calls, split before upload. There's no built-in concatenation across analyses (yet).
  </Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
  <Card title="Rep intelligence" icon="user-magnifying-glass" href="/guides/rep-intelligence">
    Persona, methodology, and synthesis at scale
  </Card>

  <Card title="Webhooks" icon="bell" href="/guides/webhooks">
    Stop polling — get notified
  </Card>
</CardGroup>
