---
title: "Analyze activity with optional durable idempotency"
method: POST
path: "/api/activities/analyze"
tags: ["Activities"]
---

# Analyze activity with optional durable idempotency

`POST /api/activities/analyze`

## Request

- Base URL: `https://courseprofiler.com`
- URL: `https://courseprofiler.com/api/activities/analyze`
- Auth: one of:
  - API key in header `X-API-Key`
  - HTTP bearer
  - none

Without idempotency, preserves ordinary analysis. Opt-in requests require app.public-api.activity-idempotency-enabled=true and qualified conditional storage. Same caller/key/input replays stored success or failure; changed input conflicts. Unresolved outcomes never trigger automatic re-execution.

## Request body

- ActivityAnalyzeRequest
  - `source` union, required
    - object
      - `kind` 'artifact', required
      - `artifact_id` string, required
    - object
      - `kind` 'url', required
      - `url` string, uri, required
    - object
      - `kind` 'raw_json', required
      - `payload` object, required
    - object
      - `kind` 'import_id', required
      - `import_id` string, required
  - `options` ActivityOptions
    - `moving_threshold` number
    - `auto_stop_threshold` number
    - `auto_stop_merge_distance` number
    - `algorithm` 'GREEDY' | 'SLOPE_BASED' | 'PACE_BASED': Omitted means source-dependent AUTO, distinct in fingerprints from an explicit algorithm.
    - `include_routes` boolean
  - `idempotency` ActivityIdempotency: Optional caller-scoped idempotency. Requires the server flag enabled. API keys are optional: anonymous callers must supply a stable private caller_token; API-key callers must omit it.
    - `key` string, required: Stable random per-submission key. Never reuse an expired key.
    - `caller_token` string: Anonymous callers only: 32 cryptographic random bytes encoded as canonical unpadded base64url. Keep private and persist before submission. Never put in URLs, logs or request IDs.

## Response `200`

Completed response; inspect job.status/errors and submission.state, not just HTTP 200

- ActivityResponse
  - `request_id` string
  - `job` ActivityJob, required: Existing PublicApiJob serialization. Empty result_artifacts, warnings, errors and progress may be omitted by NON_EMPTY; no change to legacy job serialization.
    - `request_id` RequestId (unresolved $ref)
    - `job_persistence` JobPersistence (unresolved $ref)
    - `submission_request_id` SubmissionRequestId (unresolved $ref)
    - `job_id` JobId, required (unresolved $ref)
    - `status` Status, required (unresolved $ref)
    - `status_url` StatusUrl, required (unresolved $ref)
    - `created_at` CreatedAt, required (unresolved $ref)
    - `started_at` StartedAt (unresolved $ref)
    - `completed_at` CompletedAt (unresolved $ref)
    - `expires_at` ExpiresAt, required (unresolved $ref)
    - `result_artifacts` ResultArtifacts (unresolved $ref)
    - `warnings` Warnings (unresolved $ref)
    - `errors` Errors (unresolved $ref)
    - `progress` Progress (unresolved $ref)
  - `summary` object, nullable, required
  - `submission` ActivitySubmission
    - `state` 'completed' | 'failed' | 'outcome_unknown', required: outcome_unknown can mean still running or interrupted. Never triggers automatic re-execution.
    - `replayed` boolean, required
    - `job_id` string, required
    - `submission_request_id` string, required
    - `job_persistence` JobPersistence, required (unresolved $ref)
    - `expires_at` string, date-time, required: Replay window: at most 30 days, capped by configured job retention. Reservation markers remain after expiry; expired keys are rejected.
  - `error` ApiProblem
    - `code` string, required
    - `message` string, required
    - `severity` 'info' | 'warning' | 'error', required
    - `field` string
    - `details` object

## Other responses

- `400`: Invalid input before reservation; not replay-recorded.
- `409`: idempotency_conflict: same caller/key, different input/options.
- `410`: submission_expired; expired key is blocked, not reused.
- `500`: Invalid stored metadata or recorded terminal failure.
- `503`: idempotency_disabled, submission_outcome_unknown, storage failure or recorded terminal storage failure. Keep the same caller/key; never blindly start a new submission.

---

[API](https://skmtc.dev/courseprofiler/apis/courseprofiler-public-api.md) · [All operations](https://skmtc.dev/courseprofiler/apis/courseprofiler-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/courseprofiler/apis/courseprofiler-public-api/revisions/e07403d7dba9?raw)
