Insights

Endpoints for creating an insight from a single document. For a walkthrough with runnable code, see Importing insights into Marvin.

EndpointScope
GET /api/v1/developer/import/insightsinsight:write
POST /api/v1/developer/import/insights/initializeinsight:write
POST {upload_url} (S3, not Marvin)
POST /api/v1/developer/import/insights/{insight_key}/completeinsight:write
GET /api/v1/developer/import/insights/{insight_key}/statusinsight:write

You'll also need GET /import/projects (project:read) to resolve a project_id. Marvin endpoints here are rate limited to 20 requests/minute per API key.


POST /api/v1/developer/import/insights/initialize #

Creates an insight upload and returns a presigned S3 URL.

bash
curl -s -X POST "$BASE_URL/api/v1/developer/import/insights/initialize" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "project_id": 42,
    "filename": "q1-research-summary.pdf"
  }' | python3 -m json.tool

Parameters:

FieldTypeRequiredDescription
project_idintegerYesID from the list projects response
filenamestringYesOriginal filename, used to determine the document type. Extension must be one of pdf, ppt, pptx, doc, docx, xls, xlsx, txt

should_publish is specified on complete, not here.

Response (201):

json
{
  "insight_key": "e5f6a7b8-...",
  "upload_url": "https://s3.amazonaws.com/bucket/",
  "upload_fields": {
    "key": "developer-uploads/10/e5f6a7b8-.../doc.pdf",
    "Content-Type": "application/pdf",
    "x-amz-credential": "...",
    "policy": "...",
    "x-amz-signature": "..."
  },
  "links": {
    "complete": "/api/v1/developer/import/insights/e5f6a7b8-.../complete",
    "status": "/api/v1/developer/import/insights/e5f6a7b8-.../status"
  }
}

There is no idempotency key on this endpoint — retrying initialize creates a new insight each time.


POST {upload_url} (S3) #

Upload the document directly to S3 via the presigned POST from initialize. All upload_fields must be sent as form fields before the file. The document is capped at 500 MB.

bash
curl -X POST "https://s3.amazonaws.com/bucket/" \
  -F "key=developer-uploads/10/e5f6a7b8-.../doc.pdf" \
  -F "Content-Type=application/pdf" \
  -F "x-amz-credential=..." \
  -F "policy=..." \
  -F "x-amz-signature=..." \
  -F "file=@q1-research-summary.pdf"

Presigned URLs expire after 1 hour. There is no retry endpoint for insights — call initialize again for a fresh URL, which creates a new insight.


POST /api/v1/developer/import/insights/{insight_key}/complete #

Signals Marvin that the S3 upload finished and starts document processing.

bash
curl -s -X POST "$BASE_URL/api/v1/developer/import/insights/$INSIGHT_KEY/complete" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "should_publish": false
  }' | python3 -m json.tool

Parameters:

FieldTypeRequiredDescription
should_publishbooleanNoPublish the insight immediately once processing finishes (default: false, insight stays a private draft)

The document type and S3 key are derived from the filename you gave at initialize time — you don't resend it here. This avoids a mismatch if a different filename were passed to complete than was used to compute the upload's S3 key.

Response (202):

json
{
  "insight_key": "e5f6a7b8-...",
  "status": "processing"
}

If the document hasn't finished uploading to S3 yet, this returns 400:

json
{
  "error": "File not found in S3. Upload the file before calling complete."
}

Idempotency: Calling this endpoint again once the insight has reached completed or failed returns 200 with the current status instead of re-triggering processing. A duplicate call that races the original request while it's still processing (processing_status stays pending for the whole duration) is also caught — the row is locked for the duration of the request, so the duplicate reliably sees the in-progress document and returns 200 with "message": "Upload is already processing; see status endpoint." instead of enqueueing a second job.


GET /api/v1/developer/import/insights/{insight_key}/status #

Returns the current processing status for the insight upload. Poll until processing_status is completed or failed.

bash
curl -s "$BASE_URL/api/v1/developer/import/insights/$INSIGHT_KEY/status" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

Response:

json
{
  "insight_key": "e5f6a7b8-...",
  "name": "q1-research-summary.pdf",
  "processing_status": "completed",
  "insight_state": 1,
  "insight_type": "pdf",
  "thumbnail_image": "https://s3.amazonaws.com/bucket/insights/thumbnail/....png",
  "created_at": "2026-07-10T00:00:00Z"
}

processing_status values:

StatusMeaning
pendingDocument not yet processed — awaiting complete or processing is in progress
completedConversion finished — insight available in Marvin
failedProcessing failed

insight_state values:

ValueMeaning
0Published — visible to the team
1Private draft — visible only to the creator
2Shared draft

An insight starts as a private draft (1) at initialize time; once processing completes it becomes 0 (published) if should_publish was true, or stays 1 otherwise.

insight_type values: pdf, ppt, sheet, doc, marvin_doc, other — derived from the uploaded file's extension.


GET /api/v1/developer/import/insights #

Lists insights imported through this API, scoped to projects accessible to your API key, newest first.

bash
curl -s "$BASE_URL/api/v1/developer/import/insights?limit=50&offset=0" \
  -H "Authorization: Bearer $TOKEN" | python3 -m json.tool

Response:

json
{
  "insights": [
    {
      "insight_key": "e5f6a7b8-...",
      "name": "q1-research-summary.pdf",
      "project_id": 42,
      "processing_status": "completed",
      "created_at": "2026-07-10T00:00:00Z"
    }
  ],
  "count": 1,
  "next": null,
  "previous": null
}

processing_status uses the same values as poll for status. Requires insight:write scope (the same scope used by initialize/complete/status — there is no separate read-only scope for insights in this API). Rate limited to 20 requests/minute per API key. Paginated with limit/offset query params (default and max limit is 50).